use crate::char_ptr_to_str;
use crate::context::PamHandle;
#[doc(no_inline)]
pub use crate::ErrorCode;
use pam_sys::pam_strerror;
use std::any::type_name;
use std::cmp::{Eq, PartialEq};
use std::error;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::hash::{Hash, Hasher};
use std::io;
use std::marker::PhantomData;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NoPayload {}
impl Display for NoPayload {
fn fmt(&self, _: &mut Formatter<'_>) -> FmtResult {
match *self {}
}
}
impl PartialEq for NoPayload {
fn eq(&self, _: &NoPayload) -> bool {
match *self {}
}
}
impl Eq for NoPayload {}
impl Hash for NoPayload {
fn hash<H: Hasher>(&self, _: &mut H) {
match *self {}
}
}
enum DisplayHelper<T> {
Some(PhantomData<T>),
None,
}
impl<T> DisplayHelper<T> {
#[inline]
fn new(option: &Option<T>) -> Self {
match option {
None => Self::None,
Some(_) => Self::Some(PhantomData),
}
}
}
impl<T> Debug for DisplayHelper<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
Self::None => write!(f, "None"),
Self::Some(_) => write!(f, "<{}>", type_name::<T>()),
}
}
}
#[must_use]
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ErrorWith<T> {
code: ErrorCode,
msg: String,
payload: Option<T>,
}
impl<T> ErrorWith<T> {
pub(crate) fn with_payload(
handle: PamHandle,
code: ErrorCode,
payload: Option<T>,
) -> ErrorWith<T> {
Self {
code,
msg: char_ptr_to_str(unsafe { pam_strerror(handle.into(), code.repr()) })
.unwrap_or("")
.into(),
payload,
}
}
pub const fn code(&self) -> ErrorCode {
self.code
}
pub fn message(&self) -> Option<&str> {
if self.msg.is_empty() {
None
} else {
Some(&self.msg)
}
}
#[rustversion::attr(since(1.48), const)]
pub fn payload(&self) -> Option<&T> {
self.payload.as_ref()
}
pub fn take_payload(&mut self) -> Option<T> {
match self.payload {
Some(_) => self.payload.take(),
None => None,
}
}
pub fn map<U>(self, func: impl FnOnce(T) -> U) -> ErrorWith<U> {
ErrorWith::<U> {
code: self.code,
msg: self.msg,
payload: self.payload.map(func),
}
}
#[inline]
pub fn into_without_payload(self) -> Error {
Error {
code: self.code,
msg: self.msg,
payload: None,
}
}
}
impl<T> Debug for ErrorWith<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if type_name::<T>() == type_name::<NoPayload>() {
f.debug_struct("pam_client::Error")
.field("code", &self.code)
.field("msg", &self.msg)
.finish()
} else {
f.debug_struct("pam_client::ErrorWith")
.field("code", &self.code)
.field("msg", &self.msg)
.field("payload", &DisplayHelper::new(&self.payload))
.finish()
}
}
}
pub type Error = ErrorWith<NoPayload>;
impl Error {
pub(crate) fn new(handle: PamHandle, code: ErrorCode) -> Error {
Self::with_payload(handle, code, None)
}
pub fn into_with_payload<T>(self, payload: T) -> ErrorWith<T> {
ErrorWith::<T> {
code: self.code,
msg: self.msg,
payload: Some(payload),
}
}
pub fn into<T>(self) -> ErrorWith<T> {
ErrorWith::<T> {
code: self.code,
msg: self.msg,
payload: None,
}
}
}
impl<T> Display for ErrorWith<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if self.msg.is_empty() {
write!(f, "<{}>", self.code as i32)
} else {
f.write_str(&self.msg)
}
}
}
impl<T> error::Error for ErrorWith<T> {}
impl<T> PartialEq for ErrorWith<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.code == other.code && self.payload == other.payload
}
}
impl<T> Eq for ErrorWith<T> where T: Eq {}
impl<T> Hash for ErrorWith<T>
where
T: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
(self.code as i32).hash(state);
self.payload.hash(state);
}
}
impl From<ErrorCode> for Error {
#[inline]
fn from(code: ErrorCode) -> Self {
Error {
code,
msg: String::new(),
payload: None,
}
}
}
impl<T: Send + Sync + Debug + 'static> From<ErrorWith<T>> for io::Error {
fn from(error: ErrorWith<T>) -> Self {
io::Error::new(
match error.code {
ErrorCode::INCOMPLETE => io::ErrorKind::Interrupted,
ErrorCode::BAD_ITEM | ErrorCode::USER_UNKNOWN => io::ErrorKind::NotFound,
ErrorCode::CRED_INSUFFICIENT | ErrorCode::PERM_DENIED => {
io::ErrorKind::PermissionDenied
}
_ => io::ErrorKind::Other,
},
Box::new(error),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conv_null::Conversation;
use crate::Context;
#[test]
fn test_basic() {
let context = Context::new("test", None, Conversation::default()).unwrap();
let error = Error::new(context.handle(), ErrorCode::CONV_ERR).into_with_payload("foo");
assert_eq!(error.payload(), Some(&"foo"));
assert!(error.message().is_some());
assert!(format!("{:?}", error).len() > 1);
let mut error = error.map(|_| usize::MIN);
assert_eq!(error.payload(), Some(&usize::MIN));
let _ = error.take_payload();
assert_eq!(error.take_payload(), None);
assert!(format!("{:?}", error).contains("None"));
let error = error.map(|_| usize::MIN);
assert_eq!(error.payload(), None);
let error = error.into_without_payload();
assert_eq!(error.payload(), None);
assert!(format!("{:?} {}", error, error).len() > 4);
assert_eq!(io::Error::from(error).kind(), io::ErrorKind::Other);
}
#[test]
fn test_no_msg() {
let error = Error::from(ErrorCode::BAD_ITEM);
assert_eq!(
format!("{}", error),
format!("<{}>", (ErrorCode::BAD_ITEM as i32))
);
assert_eq!(format!("{:?}", &error), format!("{:?}", error.clone()));
assert!(error.message().is_none());
let error: ErrorWith<()> = error.into();
assert_eq!(io::Error::from(error).kind(), io::ErrorKind::NotFound);
}
#[test]
fn test_traits() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn calc_hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
let error = Error::from(ErrorCode::BUF_ERR);
assert_eq!(calc_hash(&error), calc_hash(&error.clone()));
assert_eq!(&error, &error.clone());
}
}