1use bon::Builder;
19use serde::{Deserialize, Serialize};
20
21use crate::ocpi_lenient_enum;
22use crate::types::validate_fields;
23use crate::types::{DateTime, Extensions, OcpiString, Url, Validate, Validator};
24
25use super::tokens::Token;
26
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32pub struct CommandResponse {
33 pub result: CommandResponseType,
35 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
37 pub extensions: Extensions,
38}
39
40impl CommandResponse {
41 #[must_use]
43 pub fn new(result: CommandResponseType) -> Self {
44 Self { result, extensions: Extensions::new() }
45 }
46}
47
48impl Validate for CommandResponse {
49 fn validate_in(&self, v: &mut Validator) {
50 validate_fields!(self, v, result);
51 }
52}
53
54#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
60#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
61#[builder(on(_, into))]
62pub struct StartSession {
63 pub response_url: Url,
65 pub token: Token,
67 pub location_id: OcpiString<39>,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub evse_uid: Option<OcpiString<39>>,
72 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
74 #[builder(default)]
75 pub extensions: Extensions,
76}
77
78impl Validate for StartSession {
79 fn validate_in(&self, v: &mut Validator) {
80 validate_fields!(self, v, response_url, token, location_id, evse_uid);
81 }
82}
83
84#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
88#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
89#[builder(on(_, into))]
90pub struct StopSession {
91 pub response_url: Url,
93 pub session_id: OcpiString<36>,
95 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
97 #[builder(default)]
98 pub extensions: Extensions,
99}
100
101impl Validate for StopSession {
102 fn validate_in(&self, v: &mut Validator) {
103 validate_fields!(self, v, response_url, session_id);
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
113#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
114#[builder(on(_, into))]
115pub struct ReserveNow {
116 pub response_url: Url,
118 pub token: Token,
120 pub expiry_date: DateTime,
122 pub reservation_id: i64,
124 pub location_id: OcpiString<39>,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub evse_uid: Option<OcpiString<39>>,
129 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
131 #[builder(default)]
132 pub extensions: Extensions,
133}
134
135impl Validate for ReserveNow {
136 fn validate_in(&self, v: &mut Validator) {
137 validate_fields!(self, v, response_url, token, expiry_date, location_id, evse_uid);
138 }
139}
140
141#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
146#[builder(on(_, into))]
147pub struct UnlockConnector {
148 pub response_url: Url,
150 pub location_id: OcpiString<39>,
152 pub evse_uid: OcpiString<39>,
154 pub connector_id: OcpiString<36>,
156 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
158 #[builder(default)]
159 pub extensions: Extensions,
160}
161
162impl Validate for UnlockConnector {
163 fn validate_in(&self, v: &mut Validator) {
164 validate_fields!(self, v, response_url, location_id, evse_uid, connector_id);
165 }
166}
167
168#[derive(Clone, Debug, PartialEq)]
170#[non_exhaustive]
171pub enum Command {
172 ReserveNow(Box<ReserveNow>),
174 StartSession(Box<StartSession>),
176 StopSession(StopSession),
178 UnlockConnector(UnlockConnector),
180}
181
182impl Command {
183 #[must_use]
185 pub fn command_type(&self) -> CommandType {
186 match self {
187 Self::ReserveNow(_) => CommandType::ReserveNow,
188 Self::StartSession(_) => CommandType::StartSession,
189 Self::StopSession(_) => CommandType::StopSession,
190 Self::UnlockConnector(_) => CommandType::UnlockConnector,
191 }
192 }
193
194 #[must_use]
196 pub fn response_url(&self) -> &Url {
197 match self {
198 Self::ReserveNow(c) => &c.response_url,
199 Self::StartSession(c) => &c.response_url,
200 Self::StopSession(c) => &c.response_url,
201 Self::UnlockConnector(c) => &c.response_url,
202 }
203 }
204}
205
206impl Validate for Command {
207 fn validate_in(&self, v: &mut Validator) {
208 match self {
209 Self::ReserveNow(c) => c.validate_in(v),
210 Self::StartSession(c) => c.validate_in(v),
211 Self::StopSession(c) => c.validate_in(v),
212 Self::UnlockConnector(c) => c.validate_in(v),
213 }
214 }
215}
216
217ocpi_lenient_enum! {
218 pub enum CommandResponseType {
225 NotSupported = "NOT_SUPPORTED",
227 Rejected = "REJECTED",
229 Accepted = "ACCEPTED",
231 Timeout = "TIMEOUT",
233 UnknownSession = "UNKNOWN_SESSION",
235 }
236}
237
238ocpi_lenient_enum! {
239 pub enum CommandType {
245 ReserveNow = "RESERVE_NOW",
247 StartSession = "START_SESSION",
249 StopSession = "STOP_SESSION",
251 UnlockConnector = "UNLOCK_CONNECTOR",
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn timeout_is_a_response_type_here_because_there_is_no_result_object() {
262 let response: CommandResponse = serde_json::from_str(r#"{"result":"TIMEOUT"}"#).unwrap();
263 assert_eq!(response.result, CommandResponseType::Timeout);
264 assert!("TIMEOUT".parse::<crate::v2_3_0::commands::CommandResponseType>().is_err());
266 assert!("TIMEOUT".parse::<crate::v2_3_0::commands::CommandResultType>().is_ok());
267 }
268
269 #[test]
270 fn cancel_reservation_arrived_after_2_1_1() {
271 assert_eq!(CommandType::ALL_KNOWN.len(), 4);
272 assert!(!CommandType::from("CANCEL_RESERVATION").is_known());
273 }
274
275 #[test]
276 fn the_reservation_id_is_an_integer() {
277 #[derive(serde::Deserialize)]
278 struct OnlyId {
279 reservation_id: i64,
280 }
281 let json = r#"{"reservation_id":42}"#;
282 let parsed: OnlyId = serde_json::from_str(json).unwrap();
283 assert_eq!(parsed.reservation_id, 42);
284 }
285}