Mercurial > crates > nonstick
diff src/conv.rs @ 77:351bdc13005e
Update the libpam module to work with the new structure.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 08 Jun 2025 01:03:46 -0400 |
parents | e58d24849e82 |
children | 002adfb98c5c |
line wrap: on
line diff
--- a/src/conv.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/conv.rs Sun Jun 08 01:03:46 2025 -0400 @@ -14,12 +14,12 @@ /// that will be presented to the user. #[non_exhaustive] pub enum Message<'a> { - MaskedPrompt(&'a MaskedPrompt<'a>), - Prompt(&'a Prompt<'a>), - RadioPrompt(&'a RadioPrompt<'a>), - BinaryPrompt(&'a BinaryPrompt<'a>), - InfoMsg(&'a InfoMsg<'a>), - ErrorMsg(&'a ErrorMsg<'a>), + Prompt(&'a QAndA<'a>), + MaskedPrompt(&'a MaskedQAndA<'a>), + RadioPrompt(&'a RadioQAndA<'a>), + BinaryPrompt(&'a BinaryQAndA<'a>), + Error(&'a ErrorMsg<'a>), + Info(&'a InfoMsg<'a>), } impl Message<'_> { @@ -33,11 +33,11 @@ /// /// fn cant_respond(message: Message) { /// match message { - /// Message::InfoMsg(i) => { + /// Message::Info(i) => { /// eprintln!("fyi, {}", i.question()); /// i.set_answer(Ok(())) /// } - /// Message::ErrorMsg(e) => { + /// Message::Error(e) => { /// eprintln!("ERROR: {}", e.question()); /// e.set_answer(Ok(())) /// } @@ -46,55 +46,19 @@ /// } /// } pub fn set_error(&self, err: ErrorCode) { - match self { + match *self { + Message::Prompt(m) => m.set_answer(Err(err)), Message::MaskedPrompt(m) => m.set_answer(Err(err)), - Message::Prompt(m) => m.set_answer(Err(err)), Message::RadioPrompt(m) => m.set_answer(Err(err)), Message::BinaryPrompt(m) => m.set_answer(Err(err)), - Message::InfoMsg(m) => m.set_answer(Err(err)), - Message::ErrorMsg(m) => m.set_answer(Err(err)), + Message::Error(m) => m.set_answer(Err(err)), + Message::Info(m) => m.set_answer(Err(err)), } } } -/// A question-and-answer pair that can be communicated in a [`Conversation`]. -/// -/// The asking side creates a `QAndA`, then converts it to a [`Message`] -/// and sends it via a [`Conversation`]. The Conversation then retrieves -/// the answer to the question (if needed) and sets the response. -/// Once control returns to the asker, the asker gets the answer from this -/// `QAndA` and uses it however it wants. -/// -/// For a more detailed explanation of how this works, -/// see [`Conversation::communicate`]. -pub trait QAndA<'a> { - /// The type of the content of the question. - type Question: Copy; - /// The type of the answer to the question. - type Answer; - - /// Converts this Q-and-A pair into a [`Message`] for the [`Conversation`]. - fn message(&self) -> Message; - - /// The contents of the question being asked. - /// - /// For instance, this might say `"Username:"` to prompt the user - /// for their name. - fn question(&self) -> Self::Question; - - /// Sets the answer to the question. - /// - /// The [`Conversation`] implementation calls this to set the answer. - /// The conversation should *always call this function*, even for messages - /// that don't have "an answer" (like error or info messages). - fn set_answer(&self, answer: Result<Self::Answer>); - - /// Gets the answer to the question. - fn answer(self) -> Result<Self::Answer>; -} - macro_rules! q_and_a { - ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $($doc:literal)*) => { + ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $val:path, $($doc:literal)*) => { $( #[doc = $doc] )* @@ -103,25 +67,34 @@ a: Cell<Result<$at>>, } - impl<'a> QAndA<'a> for $name<'a> { - type Question = $qt; - type Answer = $at; + impl<'a> $name<'a> { + /// Converts this Q&A into a [`Message`] for the [`Conversation`]. + pub fn message(&self) -> Message { + $val(self) + } - fn question(&self) -> Self::Question { + /// The contents of the question being asked. + /// + /// For instance, this might say `"Username:"` to prompt the user + /// for their name, or the text of an error message. + pub fn question(&self) -> $qt { self.q } - fn set_answer(&self, answer: Result<Self::Answer>) { + /// Sets the answer to the question. + /// + /// The [`Conversation`] implementation calls this to set the answer. + /// The conversation should *always call this function*, + /// even for Q&A messages that don't have "an answer" + /// (like error or info messages). + pub fn set_answer(&self, answer: Result<$at>) { self.a.set(answer) } - fn answer(self) -> Result<Self::Answer> { + /// Gets the answer to the question. + pub fn answer(self) -> Result<$at> { self.a.into_inner() } - - fn message(&self) -> Message { - Message::$name(self) - } } }; } @@ -130,7 +103,7 @@ ($t:ident) => { impl<'a> $t<'a> { #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] - fn ask(question: &'a str) -> Self { + pub fn new(question: &'a str) -> Self { Self { q: question, a: Cell::new(Err(ErrorCode::ConversationError)), @@ -141,35 +114,39 @@ } q_and_a!( - MaskedPrompt<'a, Q=&'a str, A=SecureString>, - "Asks the user for data and does not echo it back while being entered." + MaskedQAndA<'a, Q=&'a str, A=SecureString>, + Message::MaskedPrompt, + "A Q&A that asks the user for text and does not show it while typing." "" "In other words, a password entry prompt." ); -ask!(MaskedPrompt); +ask!(MaskedQAndA); q_and_a!( - Prompt<'a, Q=&'a str, A=String>, - "Asks the user for data." + QAndA<'a, Q=&'a str, A=String>, + Message::Prompt, + "A standard Q&A prompt that asks the user for text." "" "This is the normal \"ask a person a question\" prompt." "When the user types, their input will be shown to them." "It can be used for things like usernames." ); -ask!(Prompt); +ask!(QAndA); q_and_a!( - RadioPrompt<'a, Q=&'a str, A=String>, - "Asks the user for \"radio button\"–style data. (Linux-PAM extension)" + RadioQAndA<'a, Q=&'a str, A=String>, + Message::RadioPrompt, + "A Q&A for \"radio button\"–style data. (Linux-PAM extension)" "" "This message type is theoretically useful for \"yes/no/maybe\"" "questions, but nowhere in the documentation is it specified" "what the format of the answer will be, or how this should be shown." ); -ask!(RadioPrompt); +ask!(RadioQAndA); q_and_a!( - BinaryPrompt<'a, Q=BinaryQuestion<'a>, A=BinaryData>, + BinaryQAndA<'a, Q=BorrowedBinaryData<'a>, A=BinaryData>, + Message::BinaryPrompt, "Asks for binary data. (Linux-PAM extension)" "" "This sends a binary message to the client application." @@ -179,29 +156,44 @@ "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> BinaryPrompt<'a> { +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. - fn ask(data: &'a [u8], data_type: u8) -> Self { + 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: BinaryQuestion { data, data_type }, + q, a: Cell::new(Err(ErrorCode::ConversationError)), } } } -/// The contents of a question requesting binary data. -/// -/// A borrowed version of [`BinaryData`]. -#[derive(Copy, Clone, Debug)] -pub struct BinaryQuestion<'a> { +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 BinaryQuestion<'_> { +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 @@ -213,11 +205,17 @@ } } +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 [`BinaryQuestion`]. +/// For borrowed data, see [`BorrowedBinaryData`]. /// You can take ownership of the stored data with `.into::<Vec<u8>>()`. -#[derive(Debug, PartialEq)] +#[derive(Debug, Default, PartialEq)] pub struct BinaryData { data: Vec<u8>, data_type: u8, @@ -238,6 +236,15 @@ } } +impl<'a> From<BorrowedBinaryData<'a>> for BinaryData { + fn from(value: BorrowedBinaryData) -> Self { + Self { + data: value.data.to_vec(), + data_type: value.data_type, + } + } +} + impl From<BinaryData> for Vec<u8> { /// Takes ownership of the data stored herein. fn from(value: BinaryData) -> Self { @@ -247,6 +254,7 @@ q_and_a!( InfoMsg<'a, Q = &'a str, A = ()>, + Message::Info, "A message containing information to be passed to the user." "" "While this does not have an answer, [`Conversation`] implementations" @@ -255,7 +263,7 @@ ); impl<'a> InfoMsg<'a> { /// Creates an informational message to send to the user. - fn new(message: &'a str) -> Self { + pub fn new(message: &'a str) -> Self { Self { q: message, a: Cell::new(Err(ErrorCode::ConversationError)), @@ -265,6 +273,7 @@ q_and_a!( ErrorMsg<'a, Q = &'a str, A = ()>, + Message::Error, "An error message to be passed to the user." "" "While this does not have an answer, [`Conversation`] implementations" @@ -274,7 +283,7 @@ ); impl<'a> ErrorMsg<'a> { /// Creates an error message to send to the user. - fn new(message: &'a str) -> Self { + pub fn new(message: &'a str) -> Self { Self { q: message, a: Cell::new(Err(ErrorCode::ConversationError)), @@ -427,27 +436,27 @@ } macro_rules! conv_fn { - ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $ask:path }) => { + ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { - let prompt = $ask($($param),*); + let prompt = <$msg>::new($($param),*); self.communicate(&[prompt.message()]); prompt.answer() } }; - ($fn_name:ident($($param:ident: $pt:ty),+) { $ask:path }) => { + ($fn_name:ident($($param:ident: $pt:ty),+) { $msg:ty }) => { fn $fn_name(&mut self, $($param: $pt),*) { - self.communicate(&[$ask($($param),*).message()]); + self.communicate(&[<$msg>::new($($param),*).message()]); } }; } impl<C: Conversation> SimpleConversation for C { - conv_fn!(prompt(message: &str) -> String { Prompt::ask }); - conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedPrompt::ask }); - conv_fn!(radio_prompt(message: &str) -> String { RadioPrompt::ask }); - conv_fn!(error_msg(message: &str) { ErrorMsg::new }); - conv_fn!(info_msg(message: &str) { InfoMsg::new }); - conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryPrompt::ask }); + conv_fn!(prompt(message: &str) -> String { QAndA }); + conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } ); + 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 }); } /// A [`Conversation`] which asks the questions one at a time. @@ -466,11 +475,11 @@ Message::RadioPrompt(prompt) => { prompt.set_answer(self.0.radio_prompt(prompt.question())) } - Message::InfoMsg(prompt) => { + Message::Info(prompt) => { self.0.info_msg(prompt.question()); prompt.set_answer(Ok(())) } - Message::ErrorMsg(prompt) => { + Message::Error(prompt) => { self.0.error_msg(prompt.question()); prompt.set_answer(Ok(())) } @@ -486,8 +495,8 @@ #[cfg(test)] mod tests { use super::{ - BinaryPrompt, Conversation, ErrorMsg, InfoMsg, MaskedPrompt, Message, Prompt, QAndA, - RadioPrompt, Result, SecureString, SimpleConversation, + BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, + RadioQAndA, Result, SecureString, SimpleConversation, }; use crate::constants::ErrorCode; use crate::BinaryData; @@ -533,11 +542,11 @@ let mut tester = DemuxTester::default(); - let what = Prompt::ask("what"); - let pass = MaskedPrompt::ask("reveal"); + let what = QAndA::new("what"); + let pass = MaskedQAndA::new("reveal"); let err = ErrorMsg::new("whoopsie"); let info = InfoMsg::new("did you know"); - let has_err = Prompt::ask("give_err"); + let has_err = QAndA::new("give_err"); let mut conv = tester.as_conversation(); @@ -563,8 +572,8 @@ let mut conv = tester.as_conversation(); - let radio = RadioPrompt::ask("channel?"); - let bin = BinaryPrompt::ask(&[10, 9, 8], 66); + let radio = RadioQAndA::new("channel?"); + let bin = BinaryQAndA::new(&[10, 9, 8], 66); conv.communicate(&[radio.message(), bin.message()]); assert_eq!("zero", radio.answer().unwrap()); @@ -578,11 +587,11 @@ fn communicate(&mut self, messages: &[Message]) { if let [msg] = messages { match *msg { - Message::InfoMsg(info) => { + Message::Info(info) => { assert_eq!("let me tell you", info.question()); info.set_answer(Ok(())) } - Message::ErrorMsg(error) => { + Message::Error(error) => { assert_eq!("oh no", error.question()); error.set_answer(Ok(())) }