Skip to main content

ocpi_kit/v2_2_1/
commands.rs

1//! The *Commands* module of OCPI 2.2.1, as a delta from
2//! [`v2_3_0::commands`](crate::v2_3_0::commands).
3//!
4//! Only [`StartSession`] and [`ReserveNow`] are redefined, because they carry a
5//! [`Token`], whose `type` differs between the versions. Everything else —
6//! the response and result objects, all three enums, `StopSession`, `CancelReservation`,
7//! `UnlockConnector` — is wire-identical.
8//!
9//! Spec: 2.2.1 §mod_commands_commands_module
10
11use bon::Builder;
12use serde::{Deserialize, Serialize};
13
14use crate::types::validate_fields;
15use crate::types::{CiString, DateTime, Extensions, Url, Validate, Validator, ViolationCode};
16
17use super::tokens::Token;
18
19// Wire-identical to OCPI 2.3.0.
20pub use crate::v2_3_0::commands::{
21    CancelReservation, CommandResponse, CommandResponseType, CommandResult, CommandResultType, CommandType,
22    StopSession, UnlockConnector,
23};
24
25/// A request to start a charging session, in OCPI 2.2.1.
26///
27/// Spec: 2.2.1 §mod_commands_startsession_object
28#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
29#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[builder(on(_, into))]
31pub struct StartSession {
32    /// URL that the [`CommandResult`] POST should be sent to.
33    pub response_url: Url,
34    /// The Token the Charge Point has to use to start a new session.
35    pub token: Token,
36    /// `Location.id` on which a session is to be started.
37    pub location_id: CiString<36>,
38    /// `EVSE.uid` on which a session is to be started. Required when `connector_id` is set.
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub evse_uid: Option<CiString<36>>,
41    /// `Connector.id` on which a session is to be started.
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub connector_id: Option<CiString<36>>,
44    /// Reference to the authorization given by the eMSP.
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub authorization_reference: Option<CiString<36>>,
47    /// Undocumented JSON fields, preserved verbatim.
48    #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
49    #[builder(default)]
50    pub extensions: Extensions,
51}
52
53impl Validate for StartSession {
54    fn validate_in(&self, v: &mut Validator) {
55        validate_fields!(
56            self,
57            v,
58            response_url,
59            token,
60            location_id,
61            evse_uid,
62            connector_id,
63            authorization_reference,
64        );
65        if self.connector_id.is_some() && self.evse_uid.is_none() {
66            v.report_at(
67                "evse_uid",
68                ViolationCode::MissingConditional,
69                "is required when `connector_id` is set",
70            );
71        }
72    }
73}
74
75/// A request to reserve an EVSE for a Token, in OCPI 2.2.1.
76///
77/// Spec: 2.2.1 §mod_commands_reservenow_object
78#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
79#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
80#[builder(on(_, into))]
81pub struct ReserveNow {
82    /// URL that the [`CommandResult`] POST should be sent to.
83    pub response_url: Url,
84    /// The Token for which to reserve the Charge Point (and specific EVSE).
85    pub token: Token,
86    /// When this reservation ends, in UTC.
87    pub expiry_date: DateTime,
88    /// Reservation id, unique for this reservation.
89    pub reservation_id: CiString<36>,
90    /// `Location.id` for which to reserve an EVSE.
91    pub location_id: CiString<36>,
92    /// `EVSE.uid` if a specific EVSE has to be reserved.
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub evse_uid: Option<CiString<36>>,
95    /// Reference to the authorization given by the eMSP.
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub authorization_reference: Option<CiString<36>>,
98    /// Undocumented JSON fields, preserved verbatim.
99    #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
100    #[builder(default)]
101    pub extensions: Extensions,
102}
103
104impl Validate for ReserveNow {
105    fn validate_in(&self, v: &mut Validator) {
106        validate_fields!(
107            self,
108            v,
109            response_url,
110            token,
111            expiry_date,
112            reservation_id,
113            location_id,
114            evse_uid,
115            authorization_reference,
116        );
117    }
118}
119
120/// Every OCPI 2.2.1 command body, tagged by the [`CommandType`] it belongs to.
121#[derive(Clone, Debug, PartialEq)]
122#[non_exhaustive]
123pub enum Command {
124    /// `POST {commands_endpoint}/CANCEL_RESERVATION`
125    CancelReservation(CancelReservation),
126    /// `POST {commands_endpoint}/RESERVE_NOW`
127    ReserveNow(Box<ReserveNow>),
128    /// `POST {commands_endpoint}/START_SESSION`
129    StartSession(Box<StartSession>),
130    /// `POST {commands_endpoint}/STOP_SESSION`
131    StopSession(StopSession),
132    /// `POST {commands_endpoint}/UNLOCK_CONNECTOR`
133    UnlockConnector(UnlockConnector),
134}
135
136impl Command {
137    /// Which command this is.
138    #[must_use]
139    pub fn command_type(&self) -> CommandType {
140        match self {
141            Self::CancelReservation(_) => CommandType::CancelReservation,
142            Self::ReserveNow(_) => CommandType::ReserveNow,
143            Self::StartSession(_) => CommandType::StartSession,
144            Self::StopSession(_) => CommandType::StopSession,
145            Self::UnlockConnector(_) => CommandType::UnlockConnector,
146        }
147    }
148
149    /// The URL the [`CommandResult`] must be POSTed to.
150    #[must_use]
151    pub fn response_url(&self) -> &Url {
152        match self {
153            Self::CancelReservation(c) => &c.response_url,
154            Self::ReserveNow(c) => &c.response_url,
155            Self::StartSession(c) => &c.response_url,
156            Self::StopSession(c) => &c.response_url,
157            Self::UnlockConnector(c) => &c.response_url,
158        }
159    }
160}
161
162impl Validate for Command {
163    fn validate_in(&self, v: &mut Validator) {
164        match self {
165            Self::CancelReservation(c) => c.validate_in(v),
166            Self::ReserveNow(c) => c.validate_in(v),
167            Self::StartSession(c) => c.validate_in(v),
168            Self::StopSession(c) => c.validate_in(v),
169            Self::UnlockConnector(c) => c.validate_in(v),
170        }
171    }
172}