# HG changeset patch # User Paul Fisher # Date 1750703194 14400 # Node ID f3e260f9ddcbf9fcadb8e19514539c2fb3f8cb33 # Parent 51c9d7e8261a6bc0a1b12cfdbf78938c505c46d5 Make conversation trait use immutable references. Since sending a conversation a message doesn't really "mutate" it, it shouldn't really be considered "mutable" for that purpose. diff -r 51c9d7e8261a -r f3e260f9ddcb src/conv.rs --- a/src/conv.rs Mon Jun 23 14:03:44 2025 -0400 +++ b/src/conv.rs Mon Jun 23 14:26:34 2025 -0400 @@ -236,7 +236,7 @@ /// as there were messages in the request; one corresponding to each. /// /// TODO: write detailed documentation about how to use this. - fn communicate(&mut self, messages: &[Message]); + fn communicate(&self, messages: &[Message]); } /// Turns a simple function into a [`Conversation`]. @@ -262,102 +262,108 @@ /// some_library::get_auth_data(&mut conversation_func(my_terminal_prompt)); /// } /// ``` -pub fn conversation_func(func: impl FnMut(&[Message])) -> impl Conversation { - Convo(func) +pub fn conversation_func(func: impl Fn(&[Message])) -> impl Conversation { + FunctionConvo(func) } -struct Convo(C); +struct FunctionConvo(C); -impl Conversation for Convo { - fn communicate(&mut self, messages: &[Message]) { +impl Conversation for FunctionConvo { + fn communicate(&self, messages: &[Message]) { self.0(messages) } } +/// A Conversation +struct UsernamePasswordConvo { + username: String, + password: String, +} + /// A conversation trait for asking or answering one question at a time. /// /// An implementation of this is provided for any [`Conversation`], /// or a PAM application can implement this trait and handle messages /// one at a time. /// -/// For example, to use a `Conversation` as a `SimpleConversation`: +/// For example, to use a `Conversation` as a `ConversationAdapter`: /// /// ``` /// # use nonstick::{Conversation, Result}; /// // Bring this trait into scope to get `masked_prompt`, among others. -/// use nonstick::SimpleConversation; +/// use nonstick::ConversationAdapter; /// -/// fn ask_for_token(convo: &mut impl Conversation) -> Result { +/// fn ask_for_token(convo: &impl Conversation) -> Result { /// convo.masked_prompt("enter your one-time token") /// } /// ``` /// -/// or to use a `SimpleConversation` as a `Conversation`: +/// or to use a `ConversationAdapter` as a `Conversation`: /// /// ``` -/// use nonstick::{Conversation, SimpleConversation}; +/// use nonstick::{Conversation, ConversationAdapter}; /// # 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: &impl Conversation) { /* ... */ /// } /// } /// /// struct MySimpleConvo {/* ... */} /// # impl MySimpleConvo { fn new() -> Self { Self{} } } /// -/// impl SimpleConversation for MySimpleConvo { +/// impl ConversationAdapter for MySimpleConvo { /// // ... -/// # fn prompt(&mut self, request: &str) -> Result { +/// # fn prompt(&self, request: &str) -> Result { /// # unimplemented!() /// # } /// # -/// # fn masked_prompt(&mut self, request: &str) -> Result { +/// # fn masked_prompt(&self, request: &str) -> Result { /// # unimplemented!() /// # } /// # -/// # fn error_msg(&mut self, message: &str) { +/// # fn error_msg(&self, message: &str) { /// # unimplemented!() /// # } /// # -/// # fn info_msg(&mut self, message: &str) { +/// # fn info_msg(&self, message: &str) { /// # unimplemented!() /// # } /// # -/// # fn radio_prompt(&mut self, request: &str) -> Result { +/// # fn radio_prompt(&self, request: &str) -> Result { /// # unimplemented!() /// # } /// # -/// # fn binary_prompt(&mut self, (data, data_type): (&[u8], u8)) -> Result { +/// # fn binary_prompt(&self, (data, data_type): (&[u8], u8)) -> Result { /// # unimplemented!() /// # } /// } /// /// fn main() { /// let mut simple = MySimpleConvo::new(); -/// some_library::get_auth_data(&mut simple.as_conversation()) +/// some_library::get_auth_data(&mut simple.into_conversation()) /// } /// ``` -pub trait SimpleConversation { +pub trait ConversationAdapter { /// Lets you use this simple conversation as a full [Conversation]. /// /// 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 into_conversation(self) -> Demux where Self: Sized, { Demux(self) } /// Prompts the user for something. - fn prompt(&mut self, request: &str) -> Result; + fn prompt(&self, request: &str) -> Result; /// Prompts the user for something, but hides what the user types. - fn masked_prompt(&mut self, request: &str) -> Result; + fn masked_prompt(&self, request: &str) -> Result; /// Alerts the user to an error. - fn error_msg(&mut self, message: &str); + fn error_msg(&self, message: &str); /// Sends an informational message to the user. - fn info_msg(&mut self, message: &str); + fn info_msg(&self, message: &str); /// \[Linux extension] Prompts the user for a yes/no/maybe conditional. /// /// PAM documentation doesn't define the format of the response. @@ -366,7 +372,7 @@ /// this will return [`ErrorCode::ConversationError`]. /// If implemented on an implementation that doesn't support radio prompts, /// this will never be called. - fn radio_prompt(&mut self, request: &str) -> Result { + fn radio_prompt(&self, request: &str) -> Result { let _ = request; Err(ErrorCode::ConversationError) } @@ -376,16 +382,22 @@ /// this will return [`ErrorCode::ConversationError`]. /// If implemented on an implementation that doesn't support radio prompts, /// this will never be called. - fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result { + fn binary_prompt(&self, data_and_type: (&[u8], u8)) -> Result { let _ = data_and_type; Err(ErrorCode::ConversationError) } } +impl From for Demux { + fn from(value: CA) -> Self { + Demux(value) + } +} + macro_rules! conv_fn { ($(#[$m:meta])* $fn_name:ident($($param:tt: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { $(#[$m])* - fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { + fn $fn_name(&self, $($param: $pt),*) -> Result<$resp_type> { let prompt = <$msg>::new($($param),*); self.communicate(&[prompt.message()]); prompt.answer() @@ -393,13 +405,13 @@ }; ($(#[$m:meta])*$fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => { $(#[$m])* - fn $fn_name(&mut self, $($param: $pt),*) { + fn $fn_name(&self, $($param: $pt),*) { self.communicate(&[<$msg>::new($($param),*).message()]); } }; } -impl SimpleConversation for C { +impl ConversationAdapter for C { conv_fn!(prompt(message: &str) -> String { QAndA }); conv_fn!(masked_prompt(message: &str) -> String { MaskedQAndA } ); conv_fn!(error_msg(message: &str) { ErrorMsg }); @@ -410,11 +422,18 @@ /// A [`Conversation`] which asks the questions one at a time. /// -/// This is automatically created by [`SimpleConversation::as_conversation`]. -pub struct Demux<'a, SC: SimpleConversation>(&'a mut SC); +/// This is automatically created by [`ConversationAdapter::into_conversation`]. +pub struct Demux(CA); -impl Conversation for Demux<'_, SC> { - fn communicate(&mut self, messages: &[Message]) { +impl Demux { + /// Gets the original Conversation out of this wrapper. + fn into_inner(self) -> CA { + self.0 + } +} + +impl Conversation for Demux { + fn communicate(&self, messages: &[Message]) { for msg in messages { match msg { Message::Prompt(prompt) => prompt.set_answer(self.0.prompt(prompt.question())), @@ -450,41 +469,41 @@ fn test_demux() { #[derive(Default)] struct DemuxTester { - error_ran: bool, - info_ran: bool, + error_ran: Cell, + info_ran: Cell, } - impl SimpleConversation for DemuxTester { - fn prompt(&mut self, request: &str) -> Result { + impl ConversationAdapter for DemuxTester { + fn prompt(&self, request: &str) -> Result { match request { "what" => Ok("whatwhat".to_owned()), "give_err" => Err(ErrorCode::PermissionDenied), _ => panic!("unexpected prompt!"), } } - fn masked_prompt(&mut self, request: &str) -> Result { + fn masked_prompt(&self, request: &str) -> Result { assert_eq!("reveal", request); Ok("my secrets".to_owned()) } - fn error_msg(&mut self, message: &str) { - self.error_ran = true; + fn error_msg(&self, message: &str) { + self.error_ran.set(true); assert_eq!("whoopsie", message); } - fn info_msg(&mut self, message: &str) { - self.info_ran = true; + fn info_msg(&self, message: &str) { + self.info_ran.set(true); assert_eq!("did you know", message); } - fn radio_prompt(&mut self, request: &str) -> Result { + fn radio_prompt(&self, request: &str) -> Result { assert_eq!("channel?", request); Ok("zero".to_owned()) } - fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result { + fn binary_prompt(&self, data_and_type: (&[u8], u8)) -> Result { assert_eq!((&[10, 9, 8][..], 66), data_and_type); Ok(BinaryData::new(vec![5, 5, 5], 5)) } } - let mut tester = DemuxTester::default(); + let tester = DemuxTester::default(); let what = QAndA::new("what"); let pass = MaskedQAndA::new("reveal"); @@ -492,7 +511,7 @@ let info = InfoMsg::new("did you know"); let has_err = QAndA::new("give_err"); - let mut conv = tester.as_conversation(); + let conv = tester.into_conversation(); // Basic tests. @@ -509,12 +528,13 @@ assert_eq!(Ok(()), err.answer()); assert_eq!(Ok(()), info.answer()); assert_eq!(ErrorCode::PermissionDenied, has_err.answer().unwrap_err()); - assert!(tester.error_ran); - assert!(tester.info_ran); + let tester = conv.into_inner(); + assert!(tester.error_ran.get()); + assert!(tester.info_ran.get()); // Test the Linux extensions separately. { - let mut conv = tester.as_conversation(); + let conv = tester.into_conversation(); let radio = RadioQAndA::new("channel?"); let bin = BinaryQAndA::new((&[10, 9, 8], 66)); @@ -529,7 +549,7 @@ struct MuxTester; impl Conversation for MuxTester { - fn communicate(&mut self, messages: &[Message]) { + fn communicate(&self, messages: &[Message]) { if let [msg] = messages { match *msg { Message::Info(info) => { @@ -567,7 +587,7 @@ } } - let mut tester = MuxTester; + let tester = MuxTester; assert_eq!("answer", tester.prompt("question").unwrap()); assert_eq!("open sesame", tester.masked_prompt("password!").unwrap()); diff -r 51c9d7e8261a -r f3e260f9ddcb src/lib.rs --- a/src/lib.rs Mon Jun 23 14:03:44 2025 -0400 +++ b/src/lib.rs Mon Jun 23 14:26:34 2025 -0400 @@ -40,7 +40,7 @@ #[doc(inline)] pub use crate::{ constants::{ErrorCode, Flags, Result}, - conv::{BinaryData, Conversation, SimpleConversation}, + conv::{BinaryData, Conversation, ConversationAdapter}, handle::{PamHandleApplication, PamHandleModule, PamShared}, module::PamModule, }; diff -r 51c9d7e8261a -r f3e260f9ddcb src/libpam/conversation.rs --- a/src/libpam/conversation.rs Mon Jun 23 14:03:44 2025 -0400 +++ b/src/libpam/conversation.rs Mon Jun 23 14:26:34 2025 -0400 @@ -63,7 +63,7 @@ } impl Conversation for LibPamConversation<'_> { - fn communicate(&mut self, messages: &[Message]) { + fn communicate(&self, messages: &[Message]) { let internal = || { let questions = Questions::new(messages)?; let mut response_pointer = std::ptr::null_mut(); diff -r 51c9d7e8261a -r f3e260f9ddcb src/libpam/handle.rs --- a/src/libpam/handle.rs Mon Jun 23 14:03:44 2025 -0400 +++ b/src/libpam/handle.rs Mon Jun 23 14:26:34 2025 -0400 @@ -127,7 +127,7 @@ } impl Conversation for LibPamHandle { - fn communicate(&mut self, messages: &[Message]) { + fn communicate(&self, messages: &[Message]) { match self.conversation_item() { Ok(conv) => conv.communicate(messages), Err(e) => { @@ -204,7 +204,7 @@ } /// Gets the `PAM_CONV` item from the handle. - fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> { + fn conversation_item(&self) -> Result<&mut LibPamConversation<'_>> { let output: *mut LibPamConversation = ptr::null_mut(); let result = unsafe { pam_ffi::pam_get_item( diff -r 51c9d7e8261a -r f3e260f9ddcb src/libpam/module.rs --- a/src/libpam/module.rs Mon Jun 23 14:03:44 2025 -0400 +++ b/src/libpam/module.rs Mon Jun 23 14:26:34 2025 -0400 @@ -12,7 +12,7 @@ /// ```no_run /// use nonstick::{ /// pam_hooks, Flags, OwnedLibPamHandle, PamHandleModule, PamModule, Result as PamResult, -/// SimpleConversation, +/// ConversationAdapter, /// }; /// use std::ffi::CStr; /// # fn main() {}