ocpi 0.3.5

Unofficial, in progress, OCPI implementation
Documentation
use commands_module::CommandRequest;
use tokio::sync::mpsc;

pub mod commands_module;
pub mod common;
pub mod credentials_module;
pub mod locations_module;
pub mod sessions_module;
pub mod types;
pub mod versions_module;

mod client;
mod context;
mod error;
mod party;
mod party_store;
mod sessions_store;
mod versions_store;

pub use {
    client::Client,
    commands_module::{CommandsModule, ResponsePromise, ResultPromise},
    common::*,
    context::{Context, ContextResult, IntoContextResult},
    credentials_module::CredentialsModule,
    error::{ClientError, Error, HubError, ServerError},
    party::Party,
    party_store::PartyStore,
    sessions_module::SessionsModule,
    sessions_store::SessionsStore,
    types::*,
    versions_module::VersionsModule,
    versions_store::{SimpleVersionsStore, VersionsStore},
};

use std::borrow::Cow;

#[cfg(feature = "warp")]
pub mod warp {
    pub use super::context::warp_extensions::*;
}

/// The Result type used by all OCPI functions.
/// Can be converted in a Response
pub type Result<T> = std::result::Result<T, Error>;

/// An Ocpi Response structure.
#[derive(serde::Serialize)]
pub struct Response {
    #[serde(skip)]
    pub http_status: http::StatusCode,

    #[serde(rename = "status_code")]
    pub code: u32,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,

    #[serde(rename = "status_message", skip_serializing_if = "Option::is_none")]
    pub message: Option<Cow<'static, str>>,

    pub timestamp: types::DateTime,

    #[serde(skip)]
    pub request_id: Option<String>,

    #[serde(skip)]
    pub correlation_id: Option<String>,
}

impl Response {
    pub fn into_http<B>(self) -> http::Response<B>
    where
        B: From<Vec<u8>>,
    {
        let http_status = self.http_status;
        let body = serde_json::to_vec(&self).expect("Serializing reply");

        let mut b = http::Response::builder()
            .status(http_status)
            .header("content-type", "application/json");

        if let Some(request_id) = self.request_id {
            b = b.header("X-Request-ID", request_id);
        }

        if let Some(correlation_id) = self.correlation_id {
            b = b.header("X-Correlation-ID", correlation_id);
        }

        b.body(body.into()).expect("Creating OCPI Response")
    }

    pub fn from_err(err: Error) -> Self {
        Self {
            http_status: err.http_status_code(),
            code: err.code(),
            data: None,
            message: Some(Cow::Owned(err.to_string())),
            timestamp: types::DateTime::now(),
            request_id: None,
            correlation_id: None,
        }
    }
}

impl<T> From<ContextResult<T>> for Response
where
    T: serde::Serialize,
{
    fn from(
        ContextResult {
            result,
            context:
                Context {
                    correlation_id,
                    request_id,
                    ..
                },
        }: ContextResult<T>,
    ) -> Self {
        match result {
            Ok(data) => Response {
                http_status: http::StatusCode::OK,
                code: 1000,
                data: Some(serde_json::to_value(&data).expect("Serializing data")),
                message: Some(Cow::Borrowed("Success")),
                timestamp: types::DateTime::now(),
                request_id: Some(request_id),
                correlation_id: Some(correlation_id),
            },

            Err(err) => Response::from_err(err),
        }
    }
}

impl<T> From<Result<T>> for Response
where
    T: serde::Serialize,
{
    fn from(res: Result<T>) -> Self {
        match res {
            Ok(data) => Response {
                http_status: http::StatusCode::OK,
                code: 1000,
                data: Some(serde_json::to_value(&data).expect("Serializing response")),
                message: Some(Cow::Borrowed("Success")),
                timestamp: types::DateTime::now(),
                request_id: None,
                correlation_id: None,
            },

            Err(err) => Response::from_err(err),
        }
    }
}

impl<T> From<Result<Paginated<T>>> for Response
where
    T: serde::Serialize,
{
    fn from(res: Result<Paginated<T>>) -> Self {
        match res {
            Ok(paginated) => Response {
                http_status: http::StatusCode::OK,
                code: 1000,
                data: Some(serde_json::to_value(&paginated.items).expect("Serializing data")),
                message: Some(Cow::Borrowed("Success")),
                timestamp: types::DateTime::now(),
                request_id: None,
                correlation_id: None,
            },

            Err(err) => Response::from_err(err),
        }
    }
}

trait CommandsHandler
where
    Self: Clone + Send + Sync + 'static,
{
}

#[derive(Clone)]
pub struct MpscCommandsHandler<P>(mpsc::Sender<CommandRequest<P>>);

#[derive(Clone)]
pub struct NoCommandsHandler;

impl<P> CommandsHandler for MpscCommandsHandler<P> where P: Party {}
impl CommandsHandler for NoCommandsHandler {}

/// Cpo implements the CPO role of the OCPI Protocol.
///
/// Every module supplies an implementation of it self
/// on this type.
#[derive(Clone)]
pub struct Cpo<DB, CH>
where
    DB: Store,
{
    db: DB,
    client: Client,
    commands_handler: CH,
}

impl<DB> Cpo<DB, NoCommandsHandler>
where
    DB: Store,
{
    /// Creates a new Cpo instance.
    /// the base_url must be the url to the base ocpi endpoint.
    /// __NOT__ the versions module.
    /// This base url will be used to add on each module url.
    /// so `versions` will be appended and the versions module
    /// should be served from that path.
    pub fn new(db: DB, client: Client) -> Self {
        Self {
            db,
            client,
            commands_handler: NoCommandsHandler,
        }
    }
}
impl<DB> Cpo<DB, NoCommandsHandler>
where
    DB: Store,
{
    pub fn with_mpsc_commands_handler(
        self,
        tx: mpsc::Sender<CommandRequest<DB::PartyModel>>,
    ) -> Cpo<DB, MpscCommandsHandler<DB::PartyModel>> {
        Cpo {
            db: self.db,
            client: self.client,
            commands_handler: MpscCommandsHandler(tx),
        }
    }
}

pub enum Authorized<P, R> {
    Party(P),
    Registration(R),
}

impl<P, R> Authorized<P, R>
where
    P: Party,
{
    pub fn party(self) -> Result<P> {
        match self {
            Self::Party(party) => Ok(party),
            _ => Err(Error::unauthorized("Invalid Token")),
        }
    }

    pub fn registration(self) -> Result<R> {
        match self {
            Self::Registration(temp) => Ok(temp),
            _ => Err(Error::unauthorized("Invalid Token")),
        }
    }
}

#[async_trait::async_trait]
pub trait Store
where
    Self: Clone + Send + Sync + 'static,
{
    type PartyModel: Party;
    type RegistrationModel: Send + Sync + 'static;

    async fn get_authorized(
        &self,
        token: types::CredentialsToken,
    ) -> Result<Authorized<Self::PartyModel, Self::RegistrationModel>>;
}