ocpi/
lib.rs

1use commands_module::CommandRequest;
2use tokio::sync::mpsc;
3
4pub mod commands_module;
5pub mod common;
6pub mod credentials_module;
7pub mod locations_module;
8pub mod sessions_module;
9pub mod types;
10pub mod versions_module;
11
12mod client;
13mod context;
14mod error;
15mod party;
16mod party_store;
17mod sessions_store;
18mod versions_store;
19
20pub use {
21    client::Client,
22    commands_module::{CommandsModule, ResponsePromise, ResultPromise},
23    common::*,
24    context::{Context, ContextResult, IntoContextResult},
25    credentials_module::CredentialsModule,
26    error::{ClientError, Error, HubError, ServerError},
27    party::Party,
28    party_store::PartyStore,
29    sessions_module::SessionsModule,
30    sessions_store::SessionsStore,
31    types::*,
32    versions_module::VersionsModule,
33    versions_store::{SimpleVersionsStore, VersionsStore},
34};
35
36use std::borrow::Cow;
37
38#[cfg(feature = "warp")]
39pub mod warp {
40    pub use super::context::warp_extensions::*;
41}
42
43/// The Result type used by all OCPI functions.
44/// Can be converted in a Response
45pub type Result<T> = std::result::Result<T, Error>;
46
47/// An Ocpi Response structure.
48#[derive(serde::Serialize)]
49pub struct Response {
50    #[serde(skip)]
51    pub http_status: http::StatusCode,
52
53    #[serde(rename = "status_code")]
54    pub code: u32,
55
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub data: Option<serde_json::Value>,
58
59    #[serde(rename = "status_message", skip_serializing_if = "Option::is_none")]
60    pub message: Option<Cow<'static, str>>,
61
62    pub timestamp: types::DateTime,
63
64    #[serde(skip)]
65    pub request_id: Option<String>,
66
67    #[serde(skip)]
68    pub correlation_id: Option<String>,
69}
70
71impl Response {
72    pub fn into_http<B>(self) -> http::Response<B>
73    where
74        B: From<Vec<u8>>,
75    {
76        let http_status = self.http_status;
77        let body = serde_json::to_vec(&self).expect("Serializing reply");
78
79        let mut b = http::Response::builder()
80            .status(http_status)
81            .header("content-type", "application/json");
82
83        if let Some(request_id) = self.request_id {
84            b = b.header("X-Request-ID", request_id);
85        }
86
87        if let Some(correlation_id) = self.correlation_id {
88            b = b.header("X-Correlation-ID", correlation_id);
89        }
90
91        b.body(body.into()).expect("Creating OCPI Response")
92    }
93
94    pub fn from_err(err: Error) -> Self {
95        Self {
96            http_status: err.http_status_code(),
97            code: err.code(),
98            data: None,
99            message: Some(Cow::Owned(err.to_string())),
100            timestamp: types::DateTime::now(),
101            request_id: None,
102            correlation_id: None,
103        }
104    }
105}
106
107impl<T> From<ContextResult<T>> for Response
108where
109    T: serde::Serialize,
110{
111    fn from(
112        ContextResult {
113            result,
114            context:
115                Context {
116                    correlation_id,
117                    request_id,
118                    ..
119                },
120        }: ContextResult<T>,
121    ) -> Self {
122        match result {
123            Ok(data) => Response {
124                http_status: http::StatusCode::OK,
125                code: 1000,
126                data: Some(serde_json::to_value(&data).expect("Serializing data")),
127                message: Some(Cow::Borrowed("Success")),
128                timestamp: types::DateTime::now(),
129                request_id: Some(request_id),
130                correlation_id: Some(correlation_id),
131            },
132
133            Err(err) => Response::from_err(err),
134        }
135    }
136}
137
138impl<T> From<Result<T>> for Response
139where
140    T: serde::Serialize,
141{
142    fn from(res: Result<T>) -> Self {
143        match res {
144            Ok(data) => Response {
145                http_status: http::StatusCode::OK,
146                code: 1000,
147                data: Some(serde_json::to_value(&data).expect("Serializing response")),
148                message: Some(Cow::Borrowed("Success")),
149                timestamp: types::DateTime::now(),
150                request_id: None,
151                correlation_id: None,
152            },
153
154            Err(err) => Response::from_err(err),
155        }
156    }
157}
158
159impl<T> From<Result<Paginated<T>>> for Response
160where
161    T: serde::Serialize,
162{
163    fn from(res: Result<Paginated<T>>) -> Self {
164        match res {
165            Ok(paginated) => Response {
166                http_status: http::StatusCode::OK,
167                code: 1000,
168                data: Some(serde_json::to_value(&paginated.items).expect("Serializing data")),
169                message: Some(Cow::Borrowed("Success")),
170                timestamp: types::DateTime::now(),
171                request_id: None,
172                correlation_id: None,
173            },
174
175            Err(err) => Response::from_err(err),
176        }
177    }
178}
179
180trait CommandsHandler
181where
182    Self: Clone + Send + Sync + 'static,
183{
184}
185
186#[derive(Clone)]
187pub struct MpscCommandsHandler<P>(mpsc::Sender<CommandRequest<P>>);
188
189#[derive(Clone)]
190pub struct NoCommandsHandler;
191
192impl<P> CommandsHandler for MpscCommandsHandler<P> where P: Party {}
193impl CommandsHandler for NoCommandsHandler {}
194
195/// Cpo implements the CPO role of the OCPI Protocol.
196///
197/// Every module supplies an implementation of it self
198/// on this type.
199#[derive(Clone)]
200pub struct Cpo<DB, CH>
201where
202    DB: Store,
203{
204    db: DB,
205    client: Client,
206    commands_handler: CH,
207}
208
209impl<DB> Cpo<DB, NoCommandsHandler>
210where
211    DB: Store,
212{
213    /// Creates a new Cpo instance.
214    /// the base_url must be the url to the base ocpi endpoint.
215    /// __NOT__ the versions module.
216    /// This base url will be used to add on each module url.
217    /// so `versions` will be appended and the versions module
218    /// should be served from that path.
219    pub fn new(db: DB, client: Client) -> Self {
220        Self {
221            db,
222            client,
223            commands_handler: NoCommandsHandler,
224        }
225    }
226}
227impl<DB> Cpo<DB, NoCommandsHandler>
228where
229    DB: Store,
230{
231    pub fn with_mpsc_commands_handler(
232        self,
233        tx: mpsc::Sender<CommandRequest<DB::PartyModel>>,
234    ) -> Cpo<DB, MpscCommandsHandler<DB::PartyModel>> {
235        Cpo {
236            db: self.db,
237            client: self.client,
238            commands_handler: MpscCommandsHandler(tx),
239        }
240    }
241}
242
243pub enum Authorized<P, R> {
244    Party(P),
245    Registration(R),
246}
247
248impl<P, R> Authorized<P, R>
249where
250    P: Party,
251{
252    pub fn party(self) -> Result<P> {
253        match self {
254            Self::Party(party) => Ok(party),
255            _ => Err(Error::unauthorized("Invalid Token")),
256        }
257    }
258
259    pub fn registration(self) -> Result<R> {
260        match self {
261            Self::Registration(temp) => Ok(temp),
262            _ => Err(Error::unauthorized("Invalid Token")),
263        }
264    }
265}
266
267#[async_trait::async_trait]
268pub trait Store
269where
270    Self: Clone + Send + Sync + 'static,
271{
272    type PartyModel: Party;
273    type RegistrationModel: Send + Sync + 'static;
274
275    async fn get_authorized(
276        &self,
277        token: types::CredentialsToken,
278    ) -> Result<Authorized<Self::PartyModel, Self::RegistrationModel>>;
279}