ocpi 0.3.5

Unofficial, in progress, OCPI implementation
Documentation
use crate::types;
use std::borrow::Cow;

#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
    #[error("Unauthorized: {0}")]
    Unauthorized(Cow<'static, str>),

    #[error("{0}")]
    MalformedJson(String),

    #[error("Internal Server error: {0}")]
    InternalServer(Cow<'static, str>),

    #[error("{0}")]
    Client(#[from] ClientError),

    #[error("{0}")]
    Server(#[from] ServerError),

    #[error("{0}")]
    Hub(#[from] HubError),
}

impl Error {
    pub fn http_status_code(&self) -> http::StatusCode {
        match self {
            Self::Unauthorized(_) => http::StatusCode::UNAUTHORIZED,
            Self::MalformedJson(_) => http::StatusCode::BAD_REQUEST,
            Self::InternalServer(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
            _ => http::StatusCode::OK,
        }
    }

    pub fn code(&self) -> u32 {
        match self {
            // Special case. To make sure that the HTTP status is communicated
            // properly.
            Self::Unauthorized(_) => 2001,
            Self::MalformedJson(_) => 2001,
            Self::InternalServer(_) => 3000,

            Self::Client(ClientError::Generic { .. }) => 2000,
            Self::Client(ClientError::InvalidParams { .. }) => 2001,
            Self::Client(ClientError::NotEnoughInformation { .. }) => 2002,
            Self::Client(ClientError::UnknownLocation { .. }) => 2003,
            Self::Client(ClientError::UnknownToken { .. }) => 2004,

            Self::Server(ServerError::Generic { .. }) => 3000,
            Self::Server(ServerError::UnusableApi { .. }) => 3001,
            Self::Server(ServerError::UnsupportedVersion { .. }) => 3002,
            Self::Server(ServerError::IncompatibleEndpoints { .. }) => 3003,

            Self::Hub(HubError::UnknownReceiver { .. }) => 4001,
            Self::Hub(HubError::Timeout { .. }) => 4002,
            Self::Hub(HubError::Connection { .. }) => 4003,
        }
    }

    pub fn unauthorized(message: impl Into<Cow<'static, str>>) -> Self {
        Self::Unauthorized(message.into())
    }

    pub fn json_input(err: &serde_json::Error) -> Self {
        if err.is_data() {
            Self::invalid_params(format!("malformed body: {}", err))
        } else {
            Self::MalformedJson(format!("invalid json in body: {}", err))
        }
    }

    pub fn invalid_params(message: impl Into<Cow<'static, str>>) -> Self {
        Self::Client(ClientError::InvalidParams {
            message: message.into(),
        })
    }

    pub fn client_generic(message: impl Into<Cow<'static, str>>) -> Self {
        Self::Client(ClientError::Generic {
            message: message.into(),
        })
    }

    pub fn server_generic(message: impl Into<Cow<'static, str>>) -> Self {
        Self::Client(ClientError::Generic {
            message: message.into(),
        })
    }

    pub fn internal_server(message: impl Into<Cow<'static, str>>) -> Self {
        Self::InternalServer(message.into())
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Self::json_input(&err)
    }
}

impl From<url::ParseError> for Error {
    fn from(err: url::ParseError) -> Self {
        Self::invalid_params(format!("Invalid URL provided: {}", err))
    }
}

impl From<tokio::time::error::Elapsed> for Error {
    fn from(_: tokio::time::error::Elapsed) -> Self {
        Self::internal_server("Internal timeout handling request")
    }
}

/// # 5.2. 2xxx: Client errors
/// Errors detected by the server in the message sent by a client
/// where the client did something wrong.
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum ClientError {
    /// # 2000
    /// Generic client error
    #[error("{message}")]
    Generic { message: Cow<'static, str> },

    /// # 2001
    /// Invalid or missing Params
    #[error("Invalid or missing parameters: {message}")]
    InvalidParams {
        /// Comma-separated list of missing or invalid params.
        message: Cow<'static, str>,
    },

    /// # 2002
    /// Not enough information, for example:
    /// Authorization request with too little information.
    #[error("Missing information: {message}")]
    NotEnoughInformation { message: Cow<'static, str> },

    /// # 2003
    /// Unknown Location, for example:
    /// Command: START_SESSION with unknown location.
    #[error("Unknown location `{id}`")]
    UnknownLocation { id: String },

    /// # 2004
    /// Unknown Token, for example:
    /// 'real-time' authorization of an unknown Token.
    #[error("Unknown token")]
    UnknownToken,
}

/// # 5.3. 3xxx: Server errors
/// Error during processing of the OCPI payload in the server.
/// The message was syntactically correct but could not be processed by the server.
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum ServerError {
    /// # 3000
    /// Generic server error
    #[error("{message}")]
    Generic { message: Cow<'static, str> },

    /// # 3001
    /// Unable to use the client’s API.
    /// For example during the credentials registration:
    /// When the initializing party requests data from the other party during the
    /// open POST call to its credentials endpoint.
    /// If one of the GETs can not be processed, the party should return this
    /// error in the POST response.
    #[error("{message}")]
    UnusableApi { message: Cow<'static, String> },

    /// # 3002
    /// Unsupported version
    #[error("Unsupported version: {0}")]
    UnsupportedVersion(types::VersionNumber),

    /// # 3003
    /// No matching endpoints or expected endpoints missing between parties.
    /// Used during the registration process if the two parties do not have any
    /// mutual modules or endpoints available, or the minimal implementation expected by
    /// the other party is not been met.
    #[error("Minimal required implementation not met")]
    IncompatibleEndpoints,
}

impl ServerError {
    pub fn unusable_api(message: impl Into<String>) -> Self {
        Self::UnusableApi {
            message: Cow::Owned(message.into()),
        }
    }
}

/// # 5.4. 4xxx: Hub errors
/// When a server encounters an error,
/// client side error (2xxx) or
/// server side error (3xxx),
/// it sends the status code to the Hub. The Hub SHALL then forward this error
/// to the client which sent the request (when the request was not a Broadcast Push).
/// For errors that a Hub encounters while routing messages,
/// the following OCPI status codes shall be used.
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum HubError {
    /// # 4001
    /// Unknown receiver (TO address is unknown)
    #[error("Unknown receiver `{target}`")]
    UnknownReceiver { target: types::Url },

    /// # 4002
    /// Timeout on forwarded request (message is forwarded, but request times out)
    #[error("Timeout calling `{target}`. Elapsed after {elapsed}")]
    Timeout {
        target: types::Url,
        elapsed: types::Duration,
    },

    /// # 4003
    /// Connection problem (receiving party is not connected)
    #[error("Could not connect to `{target}`. Target is down")]
    Connection { target: types::Url },
}

#[cfg(feature = "axum")]
mod axum_extensions {
    use super::Error;
    use axum::{
        response::{IntoResponse, Response},
        Json,
    };

    impl IntoResponse for Error {
        fn into_response(self) -> Response {
            let http_status = self.http_status_code();
            let body =
                serde_json::to_value(&crate::Response::from_err(self)).expect("Serializing body");

            (http_status, Json(body)).into_response()
        }
    }
}

#[cfg(feature = "warp")]
mod warp_extensions {
    impl warp::reject::Reject for super::Error {}
}