Mercurial > crates > nonstick
diff src/conv.rs @ 143:ebb71a412b58
Turn everything into OsString and Just Walk Out! for strings with nul.
To reduce the hazard surface of the API, this replaces most uses of &str
with &OsStr (and likewise with String/OsString).
Also, I've decided that instead of dealing with callers putting `\0`
in their parameters, I'm going to follow the example of std::env and
Just Walk Out! (i.e., panic!()).
This makes things a lot less annoying for both me and (hopefully) users.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 05 Jul 2025 22:12:46 -0400 |
parents | 80c07e5ab22f |
children | 1bc52025156b |
line wrap: on
line diff
--- a/src/conv.rs Sat Jul 05 21:49:27 2025 -0400 +++ b/src/conv.rs Sat Jul 05 22:12:46 2025 -0400 @@ -5,6 +5,7 @@ use crate::constants::{ErrorCode, Result}; use std::cell::Cell; +use std::ffi::{OsStr, OsString}; use std::fmt; use std::fmt::Debug; use std::result::Result as StdResult; @@ -31,13 +32,15 @@ /// use nonstick::ErrorCode; /// /// fn cant_respond(message: Exchange) { + /// // "question" is kind of a bad name in the context of + /// // a one-way message, but it's for consistency. /// match message { /// Exchange::Info(i) => { - /// eprintln!("fyi, {}", i.question()); + /// eprintln!("fyi, {:?}", i.question()); /// i.set_answer(Ok(())) /// } /// Exchange::Error(e) => { - /// eprintln!("ERROR: {}", e.question()); + /// eprintln!("ERROR: {:?}", e.question()); /// e.set_answer(Ok(())) /// } /// // We can't answer any questions. @@ -118,7 +121,7 @@ /// A Q&A that asks the user for text and does not show it while typing. /// /// In other words, a password entry prompt. - MaskedQAndA<'a, Q=&'a str, A=String>, + MaskedQAndA<'a, Q=&'a OsStr, A=OsString>, Exchange::MaskedPrompt ); @@ -128,7 +131,7 @@ /// 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. - QAndA<'a, Q=&'a str, A=String>, + QAndA<'a, Q=&'a OsStr, A=OsString>, Exchange::Prompt ); @@ -138,7 +141,7 @@ /// 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. - RadioQAndA<'a, Q=&'a str, A=String>, + RadioQAndA<'a, Q=&'a OsStr, A=OsString>, Exchange::RadioPrompt ); @@ -203,7 +206,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). - InfoMsg<'a, Q = &'a str, A = ()>, + InfoMsg<'a, Q = &'a OsStr, A = ()>, Exchange::Info ); @@ -213,7 +216,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). - ErrorMsg<'a, Q = &'a str, A = ()>, + ErrorMsg<'a, Q = &'a OsStr, A = ()>, Exchange::Error ); @@ -286,10 +289,11 @@ /// /// ``` /// # use nonstick::{Conversation, Result}; +/// # use std::ffi::OsString; /// // Bring this trait into scope to get `masked_prompt`, among others. /// use nonstick::ConversationAdapter; /// -/// fn ask_for_token(convo: &impl Conversation) -> Result<String> { +/// fn ask_for_token(convo: &impl Conversation) -> Result<OsString> { /// convo.masked_prompt("enter your one-time token") /// } /// ``` @@ -299,6 +303,7 @@ /// ``` /// use nonstick::{Conversation, ConversationAdapter}; /// # use nonstick::{BinaryData, Result}; +/// # use std::ffi::{OsStr, OsString}; /// mod some_library { /// # use nonstick::Conversation; /// pub fn get_auth_data(conv: &impl Conversation) { /* ... */ @@ -310,23 +315,23 @@ /// /// impl ConversationAdapter for MySimpleConvo { /// // ... -/// # fn prompt(&self, request: &str) -> Result<String> { +/// # fn prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { /// # unimplemented!() /// # } /// # -/// # fn masked_prompt(&self, request: &str) -> Result<String> { +/// # fn masked_prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { /// # unimplemented!() /// # } /// # -/// # fn error_msg(&self, message: &str) { +/// # fn error_msg(&self, message: impl AsRef<OsStr>) { /// # unimplemented!() /// # } /// # -/// # fn info_msg(&self, message: &str) { +/// # fn info_msg(&self, message: impl AsRef<OsStr>) { /// # unimplemented!() /// # } /// # -/// # fn radio_prompt(&self, request: &str) -> Result<String> { +/// # fn radio_prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { /// # unimplemented!() /// # } /// # @@ -353,13 +358,13 @@ Demux(self) } /// Prompts the user for something. - fn prompt(&self, request: &str) -> Result<String>; + fn prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString>; /// Prompts the user for something, but hides what the user types. - fn masked_prompt(&self, request: &str) -> Result<String>; + fn masked_prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString>; /// Alerts the user to an error. - fn error_msg(&self, message: &str); + fn error_msg(&self, message: impl AsRef<OsStr>); /// Sends an informational message to the user. - fn info_msg(&self, message: &str); + fn info_msg(&self, message: impl AsRef<OsStr>); /// \[Linux extension] Prompts the user for a yes/no/maybe conditional. /// /// PAM documentation doesn't define the format of the response. @@ -368,7 +373,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(&self, request: &str) -> Result<String> { + fn radio_prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { let _ = request; Err(ErrorCode::ConversationError) } @@ -391,29 +396,33 @@ } macro_rules! conv_fn { - ($(#[$m:meta])* $fn_name:ident($($param:tt: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { + ($(#[$m:meta])* $fn_name:ident($param:tt: $pt:ty) -> $resp_type:ty { $msg:ty }) => { $(#[$m])* - fn $fn_name(&self, $($param: $pt),*) -> Result<$resp_type> { - let prompt = <$msg>::new($($param),*); + fn $fn_name(&self, $param: impl AsRef<$pt>) -> Result<$resp_type> { + let prompt = <$msg>::new($param.as_ref()); self.communicate(&[prompt.exchange()]); prompt.answer() } }; - ($(#[$m:meta])*$fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => { + ($(#[$m:meta])*$fn_name:ident($param:tt: $pt:ty) { $msg:ty }) => { $(#[$m])* - fn $fn_name(&self, $($param: $pt),*) { - self.communicate(&[<$msg>::new($($param),*).exchange()]); + fn $fn_name(&self, $param: impl AsRef<$pt>) { + self.communicate(&[<$msg>::new($param.as_ref()).exchange()]); } }; } impl<C: Conversation> 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 }); - conv_fn!(info_msg(message: &str) { InfoMsg }); - conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); - conv_fn!(binary_prompt((data, data_type): (&[u8], u8)) -> BinaryData { BinaryQAndA }); + conv_fn!(prompt(message: OsStr) -> OsString { QAndA }); + conv_fn!(masked_prompt(message: OsStr) -> OsString { MaskedQAndA } ); + conv_fn!(error_msg(message: OsStr) { ErrorMsg }); + conv_fn!(info_msg(message: OsStr) { InfoMsg }); + conv_fn!(radio_prompt(message: OsStr) -> OsString { RadioQAndA }); + fn binary_prompt(&self, (data, typ): (&[u8], u8)) -> Result<BinaryData> { + let prompt = BinaryQAndA::new((data, typ)); + self.communicate(&[prompt.exchange()]); + prompt.answer() + } } /// A [`Conversation`] which asks the questions one at a time. @@ -459,7 +468,6 @@ #[cfg(test)] mod tests { use super::*; - use crate::constants::ErrorCode; #[test] fn test_demux() { @@ -470,28 +478,28 @@ } impl ConversationAdapter for DemuxTester { - fn prompt(&self, request: &str) -> Result<String> { - match request { - "what" => Ok("whatwhat".to_owned()), + fn prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { + match request.as_ref().to_str().unwrap() { + "what" => Ok("whatwhat".into()), "give_err" => Err(ErrorCode::PermissionDenied), _ => panic!("unexpected prompt!"), } } - fn masked_prompt(&self, request: &str) -> Result<String> { - assert_eq!("reveal", request); - Ok("my secrets".to_owned()) + fn masked_prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { + assert_eq!("reveal", request.as_ref()); + Ok("my secrets".into()) } - fn error_msg(&self, message: &str) { + fn error_msg(&self, message: impl AsRef<OsStr>) { self.error_ran.set(true); - assert_eq!("whoopsie", message); + assert_eq!("whoopsie", message.as_ref()); } - fn info_msg(&self, message: &str) { + fn info_msg(&self, message: impl AsRef<OsStr>) { self.info_ran.set(true); - assert_eq!("did you know", message); + assert_eq!("did you know", message.as_ref()); } - fn radio_prompt(&self, request: &str) -> Result<String> { - assert_eq!("channel?", request); - Ok("zero".to_owned()) + fn radio_prompt(&self, request: impl AsRef<OsStr>) -> Result<OsString> { + assert_eq!("channel?", request.as_ref()); + Ok("zero".into()) } fn binary_prompt(&self, data_and_type: (&[u8], u8)) -> Result<BinaryData> { assert_eq!((&[10, 9, 8][..], 66), data_and_type); @@ -501,11 +509,11 @@ let tester = DemuxTester::default(); - 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 = QAndA::new("give_err"); + let what = QAndA::new("what".as_ref()); + let pass = MaskedQAndA::new("reveal".as_ref()); + let err = ErrorMsg::new("whoopsie".as_ref()); + let info = InfoMsg::new("did you know".as_ref()); + let has_err = QAndA::new("give_err".as_ref()); let conv = tester.into_conversation(); @@ -532,7 +540,7 @@ { let conv = tester.into_conversation(); - let radio = RadioQAndA::new("channel?"); + let radio = RadioQAndA::new("channel?".as_ref()); let bin = BinaryQAndA::new((&[10, 9, 8], 66)); conv.communicate(&[radio.exchange(), bin.exchange()]); @@ -556,11 +564,13 @@ assert_eq!("oh no", error.question()); error.set_answer(Ok(())) } - Exchange::Prompt(prompt) => prompt.set_answer(match prompt.question() { - "should_err" => Err(ErrorCode::PermissionDenied), - "question" => Ok("answer".to_owned()), - other => panic!("unexpected question {other:?}"), - }), + Exchange::Prompt(prompt) => { + prompt.set_answer(match prompt.question().to_str().unwrap() { + "should_err" => Err(ErrorCode::PermissionDenied), + "question" => Ok("answer".into()), + other => panic!("unexpected question {other:?}"), + }) + } Exchange::MaskedPrompt(ask) => { assert_eq!("password!", ask.question()); ask.set_answer(Ok("open sesame".into())) @@ -571,7 +581,7 @@ } Exchange::RadioPrompt(ask) => { assert_eq!("radio?", ask.question()); - ask.set_answer(Ok("yes".to_owned())) + ask.set_answer(Ok("yes".into())) } } } else {