#
cargo_credential/
error.rs

    1use serde::{Deserialize, Serialize};
    2use std::error::Error as StdError;
    3use thiserror::Error as ThisError;
    4
    5/// Credential provider error type.
    6///
    7/// `UrlNotSupported` and `NotFound` errors both cause Cargo
    8/// to attempt another provider, if one is available. The other
    9/// variants are fatal.
    10///
    11/// Note: Do not add a tuple variant, as it cannot be serialized.
    12#[derive(Serialize, Deserialize, ThisError, Debug)]
    13#[serde(rename_all = "kebab-case", tag = "kind")]
    14#[non_exhaustive]
    15pub enum Error {
    16    /// Registry URL is not supported. This should be used if
    17    /// the provider only works for some registries. Cargo will
    18    /// try another provider, if available
    19    #[error("registry not supported")]
    20    UrlNotSupported,
    21
    22    /// Credentials could not be found. Cargo will try another
    23    /// provider, if available
    24    #[error("credential not found")]
    25    NotFound,
    26
    27    /// The provider doesn't support this operation, such as
    28    /// a provider that can't support 'login' / 'logout'
    29    #[error("requested operation not supported")]
    30    OperationNotSupported,
    31
    32    /// The provider failed to perform the operation. Other
    33    /// providers will not be attempted
    34    #[error(transparent)]
    35    #[serde(with = "error_serialize")]
    36    Other(Box<dyn StdError + Sync + Send>),
    37
    38    /// A new variant was added to this enum since Cargo was built
    39    #[error("unknown error kind; try updating Cargo?")]
    40    #[serde(other)]
    41    Unknown,
    42}
    43
    44impl From<String> for Error {
    45    fn from(message: String) -> Self {
    46        Box::new(StringTypedError {
    47            message,
    48            source: None,
    49        })
    50        .into()
    51    }
    52}
    53
    54impl From<&str> for Error {
    55    fn from(err: &str) -> Self {
    56        err.to_string().into()
    57    }
    58}
    59
    60impl From<anyhow::Error> for Error {
    61    fn from(value: anyhow::Error) -> Self {
    62        let mut prev = None;
    63        for e in value.chain().rev() {
    64            prev = Some(Box::new(StringTypedError {
    65                message: e.to_string(),
    66                source: prev,
    67            }));
    68        }
    69        Error::Other(prev.unwrap())
    70    }
    71}
    72
    73impl<T: StdError + Send + Sync + 'static> From<Box<T>> for Error {
    74    fn from(value: Box<T>) -> Self {
    75        Error::Other(value)
    76    }
    77}
    78
    79/// String-based error type with an optional source
    80#[derive(Debug)]
    81struct StringTypedError {
    82    message: String,
    83    source: Option<Box<StringTypedError>>,
    84}
    85
    86impl StdError for StringTypedError {
    87    fn source(&self) -> Option<&(dyn StdError + 'static)> {
    88        self.source.as_ref().map(|err| err as &dyn StdError)
    89    }
    90}
    91
    92impl std::fmt::Display for StringTypedError {
    93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    94        self.message.fmt(f)
    95    }
    96}
    97
    98/// Serializer / deserializer for any boxed error.
    99/// The string representation of the error, and its `source` chain can roundtrip across
    100/// the serialization. The actual types are lost (downcast will not work).
    101mod error_serialize {
    102    use std::error::Error as StdError;
    103    use std::ops::Deref;
    104
    105    use serde::{Deserialize, Deserializer, Serializer, ser::SerializeStruct};
    106
    107    use crate::error::StringTypedError;
    108
    109    pub fn serialize<S>(
    110        e: &Box<dyn StdError + Send + Sync>,
    111        serializer: S,
    112    ) -> Result<S::Ok, S::Error>
    113    where
    114        S: Serializer,
    115    {
    116        let mut state = serializer.serialize_struct("StringTypedError", 2)?;
    117        state.serialize_field("message", &format!("{}", e))?;
    118
    119        // Serialize the source error chain recursively
    120        let mut current_source: &dyn StdError = e.deref();
    121        let mut sources = Vec::new();
    122        while let Some(err) = current_source.source() {
    123            sources.push(err.to_string());
    124            current_source = err;
    125        }
    126        state.serialize_field("caused-by", &sources)?;
    127        state.end()
    128    }
    129
    130    pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<dyn StdError + Sync + Send>, D::Error>
    131    where
    132        D: Deserializer<'de>,
    133    {
    134        #[derive(Deserialize)]
    135        #[serde(rename_all = "kebab-case")]
    136        struct ErrorData {
    137            message: String,
    138            caused_by: Option<Vec<String>>,
    139        }
    140        let data = ErrorData::deserialize(deserializer)?;
    141        let mut prev = None;
    142        if let Some(source) = data.caused_by {
    143            for e in source.into_iter().rev() {
    144                prev = Some(Box::new(StringTypedError {
    145                    message: e,
    146                    source: prev,
    147                }));
    148            }
    149        }
    150        let e = Box::new(StringTypedError {
    151            message: data.message,
    152            source: prev,
    153        });
    154        Ok(e)
    155    }
    156}
    157
    158#[cfg(test)]
    159mod tests {
    160    use super::Error;
    161
    162    #[test]
    163    pub fn unknown_kind() {
    164        let json = r#"{
    165            "kind": "unexpected-kind",
    166            "unexpected-content": "test"
    167          }"#;
    168        let e: Error = serde_json::from_str(&json).unwrap();
    169        assert!(matches!(e, Error::Unknown));
    170    }
    171
    172    #[test]
    173    pub fn roundtrip() {
    174        // Construct an error with context
    175        let e = anyhow::anyhow!("E1").context("E2").context("E3");
    176        // Convert to a string with contexts.
    177        let s1 = format!("{:?}", e);
    178        // Convert the error into an `Error`
    179        let e: Error = e.into();
    180        // Convert that error into JSON
    181        let json = serde_json::to_string_pretty(&e).unwrap();
    182        // Convert that error back to anyhow
    183        let e: anyhow::Error = e.into();
    184        let s2 = format!("{:?}", e);
    185        assert_eq!(s1, s2);
    186
    187        // Convert the error back from JSON
    188        let e: Error = serde_json::from_str(&json).unwrap();
    189        // Convert to back to anyhow
    190        let e: anyhow::Error = e.into();
    191        let s3 = format!("{:?}", e);
    192        assert_eq!(s2, s3);
    193
    194        assert_eq!(
    195            r#"{
    196  "kind": "other",
    197  "message": "E3",
    198  "caused-by": [
    199    "E2",
    200    "E1"
    201  ]
    202}"#,
    203            json
    204        );
    205    }
    206}