1use crate::char_ptr_to_str;
12use crate::context::PamHandle;
13#[doc(no_inline)]
14pub use crate::ErrorCode;
15use pam_sys::pam_strerror;
16
17use std::any::type_name;
18use std::cmp::{Eq, PartialEq};
19use std::error;
20use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
21use std::hash::{Hash, Hasher};
22use std::io;
23use std::marker::PhantomData;
24
25#[derive(Copy, Clone, Debug)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum NoPayload {}
34
35impl Display for NoPayload {
36 fn fmt(&self, _: &mut Formatter<'_>) -> FmtResult {
37 match *self {}
38 }
39}
40
41impl PartialEq for NoPayload {
42 fn eq(&self, _: &NoPayload) -> bool {
43 match *self {}
44 }
45}
46
47impl Eq for NoPayload {}
48
49impl Hash for NoPayload {
50 fn hash<H: Hasher>(&self, _: &mut H) {
51 match *self {}
52 }
53}
54
55enum DisplayHelper<T> {
57 Some(PhantomData<T>),
58 None,
59}
60
61impl<T> DisplayHelper<T> {
62 #[inline]
63 fn new(option: &Option<T>) -> Self {
64 match option {
65 None => Self::None,
66 Some(_) => Self::Some(PhantomData),
67 }
68 }
69}
70
71impl<T> Debug for DisplayHelper<T> {
72 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
73 match *self {
74 Self::None => write!(f, "None"),
75 Self::Some(_) => write!(f, "<{}>", type_name::<T>()),
76 }
77 }
78}
79
80#[must_use]
85#[derive(Clone)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87pub struct ErrorWith<T> {
88 code: ErrorCode,
89 msg: String,
90 payload: Option<T>,
91}
92
93impl<T> ErrorWith<T> {
94 pub(crate) fn with_payload(
99 handle: PamHandle,
100 code: ErrorCode,
101 payload: Option<T>,
102 ) -> ErrorWith<T> {
103 Self {
104 code,
105 msg: char_ptr_to_str(unsafe { pam_strerror(handle.into(), code.repr()) })
106 .unwrap_or("")
107 .into(),
108 payload,
109 }
110 }
111
112 pub const fn code(&self) -> ErrorCode {
114 self.code
115 }
116
117 pub fn message(&self) -> Option<&str> {
119 if self.msg.is_empty() {
120 None
121 } else {
122 Some(&self.msg)
123 }
124 }
125
126 #[rustversion::attr(since(1.48), const)]
128 pub fn payload(&self) -> Option<&T> {
129 self.payload.as_ref()
130 }
131
132 pub fn take_payload(&mut self) -> Option<T> {
138 match self.payload {
139 Some(_) => self.payload.take(),
140 None => None,
141 }
142 }
143
144 pub fn map<U>(self, func: impl FnOnce(T) -> U) -> ErrorWith<U> {
146 ErrorWith::<U> {
147 code: self.code,
148 msg: self.msg,
149 payload: self.payload.map(func),
150 }
151 }
152
153 #[inline]
155 pub fn into_without_payload(self) -> Error {
156 Error {
157 code: self.code,
158 msg: self.msg,
159 payload: None,
160 }
161 }
162}
163
164impl<T> Debug for ErrorWith<T> {
165 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
166 if type_name::<T>() == type_name::<NoPayload>() {
169 f.debug_struct("pam_client::Error")
170 .field("code", &self.code)
171 .field("msg", &self.msg)
172 .finish()
173 } else {
174 f.debug_struct("pam_client::ErrorWith")
175 .field("code", &self.code)
176 .field("msg", &self.msg)
177 .field("payload", &DisplayHelper::new(&self.payload))
178 .finish()
179 }
180 }
181}
182
183pub type Error = ErrorWith<NoPayload>;
187
188impl Error {
189 pub(crate) fn new(handle: PamHandle, code: ErrorCode) -> Error {
191 Self::with_payload(handle, code, None)
192 }
193
194 pub fn into_with_payload<T>(self, payload: T) -> ErrorWith<T> {
197 ErrorWith::<T> {
198 code: self.code,
199 msg: self.msg,
200 payload: Some(payload),
201 }
202 }
203
204 pub fn into<T>(self) -> ErrorWith<T> {
207 ErrorWith::<T> {
208 code: self.code,
209 msg: self.msg,
210 payload: None,
211 }
212 }
213}
214
215impl<T> Display for ErrorWith<T> {
216 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
217 if self.msg.is_empty() {
218 write!(f, "<{}>", self.code as i32)
219 } else {
220 f.write_str(&self.msg)
221 }
222 }
223}
224
225impl<T> error::Error for ErrorWith<T> {}
226
227impl<T> PartialEq for ErrorWith<T>
228where
229 T: PartialEq,
230{
231 fn eq(&self, other: &Self) -> bool {
232 self.code == other.code && self.payload == other.payload
233 }
234}
235
236impl<T> Eq for ErrorWith<T> where T: Eq {}
237
238impl<T> Hash for ErrorWith<T>
239where
240 T: Hash,
241{
242 fn hash<H: Hasher>(&self, state: &mut H) {
243 (self.code as i32).hash(state);
244 self.payload.hash(state);
245 }
246}
247
248impl From<ErrorCode> for Error {
268 #[inline]
269 fn from(code: ErrorCode) -> Self {
270 Error {
271 code,
272 msg: String::new(),
273 payload: None,
274 }
275 }
276}
277
278impl<T: Send + Sync + Debug + 'static> From<ErrorWith<T>> for io::Error {
301 fn from(error: ErrorWith<T>) -> Self {
302 io::Error::new(
303 match error.code {
304 ErrorCode::INCOMPLETE => io::ErrorKind::Interrupted,
305 ErrorCode::BAD_ITEM | ErrorCode::USER_UNKNOWN => io::ErrorKind::NotFound,
306 ErrorCode::CRED_INSUFFICIENT | ErrorCode::PERM_DENIED => {
307 io::ErrorKind::PermissionDenied
308 }
309 _ => io::ErrorKind::Other,
310 },
311 Box::new(error),
312 )
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::conv_null::Conversation;
320 use crate::Context;
321
322 #[test]
323 fn test_basic() {
324 let context = Context::new("test", None, Conversation::default()).unwrap();
325 let error = Error::new(context.handle(), ErrorCode::CONV_ERR).into_with_payload("foo");
326 assert_eq!(error.payload(), Some(&"foo"));
327 assert!(error.message().is_some());
328 assert!(format!("{:?}", error).len() > 1);
329 let mut error = error.map(|_| usize::MIN);
330 assert_eq!(error.payload(), Some(&usize::MIN));
331 let _ = error.take_payload();
332 assert_eq!(error.take_payload(), None);
333 assert!(format!("{:?}", error).contains("None"));
334 let error = error.map(|_| usize::MIN);
335 assert_eq!(error.payload(), None);
336 let error = error.into_without_payload();
337 assert_eq!(error.payload(), None);
338 assert!(format!("{:?} {}", error, error).len() > 4);
339 assert_eq!(io::Error::from(error).kind(), io::ErrorKind::Other);
340 }
341
342 #[test]
343 fn test_no_msg() {
344 let error = Error::from(ErrorCode::BAD_ITEM);
345 assert_eq!(
346 format!("{}", error),
347 format!("<{}>", (ErrorCode::BAD_ITEM as i32))
348 );
349 assert_eq!(format!("{:?}", &error), format!("{:?}", error.clone()));
350 assert!(error.message().is_none());
351 let error: ErrorWith<()> = error.into();
352 assert_eq!(io::Error::from(error).kind(), io::ErrorKind::NotFound);
353 }
354
355 #[test]
357 fn test_traits() {
358 use std::collections::hash_map::DefaultHasher;
359 use std::hash::{Hash, Hasher};
360
361 fn calc_hash<T: Hash>(t: &T) -> u64 {
362 let mut s = DefaultHasher::new();
363 t.hash(&mut s);
364 s.finish()
365 }
366
367 let error = Error::from(ErrorCode::BUF_ERR);
368
369 assert_eq!(calc_hash(&error), calc_hash(&error.clone()));
370 assert_eq!(&error, &error.clone());
371 }
372}