1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use crate::media_container::server::Feature;
use isahc::{AsyncBody, AsyncReadResponseExt, Response as HttpResponse};
use serde::Deserialize;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
    #[error("Authenticated client must be provided.")]
    ClientNotAuthenticated,
    #[error("Non-authenticated client must be provided.")]
    ClientAuthenticated,
    #[error("Unable to deserialize JSON: {source}.")]
    JsonDeserealiseError {
        #[from]
        source: serde_json::Error,
    },
    #[error("Unable to deserialize XML: {source}.")]
    XmlDeserealiseError {
        #[from]
        source: quick_xml::de::DeError,
    },
    #[error("{source}")]
    UrlencodingError {
        #[from]
        source: serde_urlencoded::ser::Error,
    },
    #[error("{source}")]
    HttpError {
        #[from]
        source: http::Error,
    },
    #[error("{source}")]
    IsahcError {
        #[from]
        source: isahc::Error,
    },
    #[error("{source}")]
    StdIoError {
        #[from]
        source: std::io::Error,
    },
    #[error("Error while communicating with MyPlexApi: {errors:?}.")]
    MyPlexErrorResponse { errors: Vec<Self> },
    #[error("Error occurred while communicating to MyPlex API: #{code} - {message}.")]
    MyPlexApiError { code: i32, message: String },
    #[error("Failed to get claim token: {0}.")]
    FailedToGetClaimToken(String),
    #[error("Unexpected API response: HTTP {status_code}, content: {content}.")]
    UnexpectedApiResponse { status_code: u16, content: String },
    #[error("The requested webhook wasn't found: {0}.")]
    WebhookNotFound(String),
    #[error("The mandatory feature is not available: {0}.")]
    SubscriptionFeatureNotAvailable(Feature),
    #[error("OTP is required for the authentication.")]
    OtpRequired,
    #[error("OTP is provided, but no username/password.")]
    UselessOtp,
    #[error("Connecting to the device is not supported.")]
    DeviceConnectionNotSupported,
    #[error("Device doesn't have any exposed connection endpoints.")]
    DeviceConnectionsIsEmpty,
}

const PLEX_API_ERROR_CODE_AUTH_OTP_REQUIRED: i32 = 1029;

impl Error {
    pub async fn from_response(mut response: HttpResponse<AsyncBody>) -> Self {
        let status_code = response.status().as_u16();
        let response_body = match response.text().await {
            Ok(body) => body,
            Err(err) => {
                return err.into();
            }
        };
        let err = serde_json::from_str::<MyPlexApiErrorResponse>(&response_body);
        match err {
            Ok(r) => {
                if r.errors.len() == 1 && r.errors[0].code == PLEX_API_ERROR_CODE_AUTH_OTP_REQUIRED
                {
                    Self::OtpRequired
                } else {
                    r.into()
                }
            }
            Err(_) => Self::UnexpectedApiResponse {
                status_code,
                content: response_body,
            },
        }
    }
}

#[derive(Deserialize, Debug, Clone)]
#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
struct MyPlexApiError {
    code: i32,
    message: String,

    /// HTTP status code for the error. Not used, keeping it here to reflect the complete API response.
    #[allow(dead_code)]
    status: u16,
}

#[derive(Deserialize, Debug, Clone)]
#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
pub(crate) struct MyPlexApiErrorResponse {
    errors: Vec<MyPlexApiError>,
}

impl From<MyPlexApiError> for Error {
    fn from(error: MyPlexApiError) -> Self {
        Self::MyPlexApiError {
            code: error.code,
            message: error.message,
        }
    }
}

impl From<MyPlexApiErrorResponse> for Error {
    fn from(r: MyPlexApiErrorResponse) -> Self {
        Self::MyPlexErrorResponse {
            errors: r.errors.into_iter().map(|e| e.into()).collect(),
        }
    }
}