Skip to main content

codetics_ocpp16/
actions.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Kevin Vavelin, Codetics (https://codetics.fr)
3
4//! OCPP 1.6 actions: message direction, request/response pairing and typed dispatch.
5
6// Call enums hold one decoded payload transiently; boxing every variant would only add allocations.
7#![allow(clippy::large_enum_variant)]
8
9use ocpp_core::validate::Violation;
10
11use crate::messages::*;
12
13impl ocpp_core::Request for AuthorizeRequest {
14    const ACTION: &'static str = "Authorize";
15    type Response = AuthorizeResponse;
16}
17
18impl ocpp_core::Request for BootNotificationRequest {
19    const ACTION: &'static str = "BootNotification";
20    type Response = BootNotificationResponse;
21}
22
23impl ocpp_core::Request for CancelReservationRequest {
24    const ACTION: &'static str = "CancelReservation";
25    type Response = CancelReservationResponse;
26}
27
28impl ocpp_core::Request for ChangeAvailabilityRequest {
29    const ACTION: &'static str = "ChangeAvailability";
30    type Response = ChangeAvailabilityResponse;
31}
32
33impl ocpp_core::Request for ChangeConfigurationRequest {
34    const ACTION: &'static str = "ChangeConfiguration";
35    type Response = ChangeConfigurationResponse;
36}
37
38impl ocpp_core::Request for ClearCacheRequest {
39    const ACTION: &'static str = "ClearCache";
40    type Response = ClearCacheResponse;
41}
42
43impl ocpp_core::Request for ClearChargingProfileRequest {
44    const ACTION: &'static str = "ClearChargingProfile";
45    type Response = ClearChargingProfileResponse;
46}
47
48impl ocpp_core::Request for DataTransferRequest {
49    const ACTION: &'static str = "DataTransfer";
50    type Response = DataTransferResponse;
51}
52
53impl ocpp_core::Request for DiagnosticsStatusNotificationRequest {
54    const ACTION: &'static str = "DiagnosticsStatusNotification";
55    type Response = DiagnosticsStatusNotificationResponse;
56}
57
58impl ocpp_core::Request for FirmwareStatusNotificationRequest {
59    const ACTION: &'static str = "FirmwareStatusNotification";
60    type Response = FirmwareStatusNotificationResponse;
61}
62
63impl ocpp_core::Request for GetCompositeScheduleRequest {
64    const ACTION: &'static str = "GetCompositeSchedule";
65    type Response = GetCompositeScheduleResponse;
66}
67
68impl ocpp_core::Request for GetConfigurationRequest {
69    const ACTION: &'static str = "GetConfiguration";
70    type Response = GetConfigurationResponse;
71}
72
73impl ocpp_core::Request for GetDiagnosticsRequest {
74    const ACTION: &'static str = "GetDiagnostics";
75    type Response = GetDiagnosticsResponse;
76}
77
78impl ocpp_core::Request for GetLocalListVersionRequest {
79    const ACTION: &'static str = "GetLocalListVersion";
80    type Response = GetLocalListVersionResponse;
81}
82
83impl ocpp_core::Request for HeartbeatRequest {
84    const ACTION: &'static str = "Heartbeat";
85    type Response = HeartbeatResponse;
86}
87
88impl ocpp_core::Request for MeterValuesRequest {
89    const ACTION: &'static str = "MeterValues";
90    type Response = MeterValuesResponse;
91}
92
93impl ocpp_core::Request for RemoteStartTransactionRequest {
94    const ACTION: &'static str = "RemoteStartTransaction";
95    type Response = RemoteStartTransactionResponse;
96}
97
98impl ocpp_core::Request for RemoteStopTransactionRequest {
99    const ACTION: &'static str = "RemoteStopTransaction";
100    type Response = RemoteStopTransactionResponse;
101}
102
103impl ocpp_core::Request for ReserveNowRequest {
104    const ACTION: &'static str = "ReserveNow";
105    type Response = ReserveNowResponse;
106}
107
108impl ocpp_core::Request for ResetRequest {
109    const ACTION: &'static str = "Reset";
110    type Response = ResetResponse;
111}
112
113impl ocpp_core::Request for SendLocalListRequest {
114    const ACTION: &'static str = "SendLocalList";
115    type Response = SendLocalListResponse;
116}
117
118impl ocpp_core::Request for SetChargingProfileRequest {
119    const ACTION: &'static str = "SetChargingProfile";
120    type Response = SetChargingProfileResponse;
121}
122
123impl ocpp_core::Request for StartTransactionRequest {
124    const ACTION: &'static str = "StartTransaction";
125    type Response = StartTransactionResponse;
126}
127
128impl ocpp_core::Request for StatusNotificationRequest {
129    const ACTION: &'static str = "StatusNotification";
130    type Response = StatusNotificationResponse;
131}
132
133impl ocpp_core::Request for StopTransactionRequest {
134    const ACTION: &'static str = "StopTransaction";
135    type Response = StopTransactionResponse;
136}
137
138impl ocpp_core::Request for TriggerMessageRequest {
139    const ACTION: &'static str = "TriggerMessage";
140    type Response = TriggerMessageResponse;
141}
142
143impl ocpp_core::Request for UnlockConnectorRequest {
144    const ACTION: &'static str = "UnlockConnector";
145    type Response = UnlockConnectorResponse;
146}
147
148impl ocpp_core::Request for UpdateFirmwareRequest {
149    const ACTION: &'static str = "UpdateFirmware";
150    type Response = UpdateFirmwareResponse;
151}
152
153/// Which side initiates a message.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
155pub enum Direction {
156    /// Charging Station → CSMS.
157    StationToCsms,
158    /// CSMS → Charging Station.
159    CsmsToStation,
160    /// Either side.
161    Both,
162}
163
164/// All actions of this OCPP version.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
166pub enum Action {
167    Authorize,
168    BootNotification,
169    CancelReservation,
170    ChangeAvailability,
171    ChangeConfiguration,
172    ClearCache,
173    ClearChargingProfile,
174    DataTransfer,
175    DiagnosticsStatusNotification,
176    FirmwareStatusNotification,
177    GetCompositeSchedule,
178    GetConfiguration,
179    GetDiagnostics,
180    GetLocalListVersion,
181    Heartbeat,
182    MeterValues,
183    RemoteStartTransaction,
184    RemoteStopTransaction,
185    ReserveNow,
186    Reset,
187    SendLocalList,
188    SetChargingProfile,
189    StartTransaction,
190    StatusNotification,
191    StopTransaction,
192    TriggerMessage,
193    UnlockConnector,
194    UpdateFirmware,
195}
196
197impl Action {
198    pub const ALL: &'static [Action] = &[
199        Action::Authorize,
200        Action::BootNotification,
201        Action::CancelReservation,
202        Action::ChangeAvailability,
203        Action::ChangeConfiguration,
204        Action::ClearCache,
205        Action::ClearChargingProfile,
206        Action::DataTransfer,
207        Action::DiagnosticsStatusNotification,
208        Action::FirmwareStatusNotification,
209        Action::GetCompositeSchedule,
210        Action::GetConfiguration,
211        Action::GetDiagnostics,
212        Action::GetLocalListVersion,
213        Action::Heartbeat,
214        Action::MeterValues,
215        Action::RemoteStartTransaction,
216        Action::RemoteStopTransaction,
217        Action::ReserveNow,
218        Action::Reset,
219        Action::SendLocalList,
220        Action::SetChargingProfile,
221        Action::StartTransaction,
222        Action::StatusNotification,
223        Action::StopTransaction,
224        Action::TriggerMessage,
225        Action::UnlockConnector,
226        Action::UpdateFirmware,
227    ];
228
229    pub const fn as_str(&self) -> &'static str {
230        match self {
231            Action::Authorize => "Authorize",
232            Action::BootNotification => "BootNotification",
233            Action::CancelReservation => "CancelReservation",
234            Action::ChangeAvailability => "ChangeAvailability",
235            Action::ChangeConfiguration => "ChangeConfiguration",
236            Action::ClearCache => "ClearCache",
237            Action::ClearChargingProfile => "ClearChargingProfile",
238            Action::DataTransfer => "DataTransfer",
239            Action::DiagnosticsStatusNotification => "DiagnosticsStatusNotification",
240            Action::FirmwareStatusNotification => "FirmwareStatusNotification",
241            Action::GetCompositeSchedule => "GetCompositeSchedule",
242            Action::GetConfiguration => "GetConfiguration",
243            Action::GetDiagnostics => "GetDiagnostics",
244            Action::GetLocalListVersion => "GetLocalListVersion",
245            Action::Heartbeat => "Heartbeat",
246            Action::MeterValues => "MeterValues",
247            Action::RemoteStartTransaction => "RemoteStartTransaction",
248            Action::RemoteStopTransaction => "RemoteStopTransaction",
249            Action::ReserveNow => "ReserveNow",
250            Action::Reset => "Reset",
251            Action::SendLocalList => "SendLocalList",
252            Action::SetChargingProfile => "SetChargingProfile",
253            Action::StartTransaction => "StartTransaction",
254            Action::StatusNotification => "StatusNotification",
255            Action::StopTransaction => "StopTransaction",
256            Action::TriggerMessage => "TriggerMessage",
257            Action::UnlockConnector => "UnlockConnector",
258            Action::UpdateFirmware => "UpdateFirmware",
259        }
260    }
261
262    pub fn from_name(s: &str) -> Option<Action> {
263        match s {
264            "Authorize" => Some(Action::Authorize),
265            "BootNotification" => Some(Action::BootNotification),
266            "CancelReservation" => Some(Action::CancelReservation),
267            "ChangeAvailability" => Some(Action::ChangeAvailability),
268            "ChangeConfiguration" => Some(Action::ChangeConfiguration),
269            "ClearCache" => Some(Action::ClearCache),
270            "ClearChargingProfile" => Some(Action::ClearChargingProfile),
271            "DataTransfer" => Some(Action::DataTransfer),
272            "DiagnosticsStatusNotification" => Some(Action::DiagnosticsStatusNotification),
273            "FirmwareStatusNotification" => Some(Action::FirmwareStatusNotification),
274            "GetCompositeSchedule" => Some(Action::GetCompositeSchedule),
275            "GetConfiguration" => Some(Action::GetConfiguration),
276            "GetDiagnostics" => Some(Action::GetDiagnostics),
277            "GetLocalListVersion" => Some(Action::GetLocalListVersion),
278            "Heartbeat" => Some(Action::Heartbeat),
279            "MeterValues" => Some(Action::MeterValues),
280            "RemoteStartTransaction" => Some(Action::RemoteStartTransaction),
281            "RemoteStopTransaction" => Some(Action::RemoteStopTransaction),
282            "ReserveNow" => Some(Action::ReserveNow),
283            "Reset" => Some(Action::Reset),
284            "SendLocalList" => Some(Action::SendLocalList),
285            "SetChargingProfile" => Some(Action::SetChargingProfile),
286            "StartTransaction" => Some(Action::StartTransaction),
287            "StatusNotification" => Some(Action::StatusNotification),
288            "StopTransaction" => Some(Action::StopTransaction),
289            "TriggerMessage" => Some(Action::TriggerMessage),
290            "UnlockConnector" => Some(Action::UnlockConnector),
291            "UpdateFirmware" => Some(Action::UpdateFirmware),
292            _ => None,
293        }
294    }
295
296    pub const fn direction(&self) -> Direction {
297        match self {
298            Action::Authorize => Direction::StationToCsms,
299            Action::BootNotification => Direction::StationToCsms,
300            Action::CancelReservation => Direction::CsmsToStation,
301            Action::ChangeAvailability => Direction::CsmsToStation,
302            Action::ChangeConfiguration => Direction::CsmsToStation,
303            Action::ClearCache => Direction::CsmsToStation,
304            Action::ClearChargingProfile => Direction::CsmsToStation,
305            Action::DataTransfer => Direction::Both,
306            Action::DiagnosticsStatusNotification => Direction::StationToCsms,
307            Action::FirmwareStatusNotification => Direction::StationToCsms,
308            Action::GetCompositeSchedule => Direction::CsmsToStation,
309            Action::GetConfiguration => Direction::CsmsToStation,
310            Action::GetDiagnostics => Direction::CsmsToStation,
311            Action::GetLocalListVersion => Direction::CsmsToStation,
312            Action::Heartbeat => Direction::StationToCsms,
313            Action::MeterValues => Direction::StationToCsms,
314            Action::RemoteStartTransaction => Direction::CsmsToStation,
315            Action::RemoteStopTransaction => Direction::CsmsToStation,
316            Action::ReserveNow => Direction::CsmsToStation,
317            Action::Reset => Direction::CsmsToStation,
318            Action::SendLocalList => Direction::CsmsToStation,
319            Action::SetChargingProfile => Direction::CsmsToStation,
320            Action::StartTransaction => Direction::StationToCsms,
321            Action::StatusNotification => Direction::StationToCsms,
322            Action::StopTransaction => Direction::StationToCsms,
323            Action::TriggerMessage => Direction::CsmsToStation,
324            Action::UnlockConnector => Direction::CsmsToStation,
325            Action::UpdateFirmware => Direction::CsmsToStation,
326        }
327    }
328
329    /// `true` for unconfirmed SEND messages (OCPP 2.1), which have no response.
330    pub const fn is_send(&self) -> bool {
331        false
332    }
333}
334
335impl std::fmt::Display for Action {
336    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337        f.write_str(self.as_str())
338    }
339}
340
341/// Why an inbound message could not be decoded.
342#[derive(Debug, Clone, PartialEq)]
343pub enum DecodeError {
344    /// The action is not part of this OCPP version.
345    UnknownAction(String),
346    /// The action exists but is not sent in this direction.
347    WrongDirection(Action),
348    /// The payload violates the message definition.
349    Invalid(Violation),
350}
351
352impl std::fmt::Display for DecodeError {
353    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354        match self {
355            DecodeError::UnknownAction(a) => write!(f, "unknown action '{a}'"),
356            DecodeError::WrongDirection(a) => {
357                write!(f, "action '{a}' is not accepted in this direction")
358            }
359            DecodeError::Invalid(v) => write!(f, "{v}"),
360        }
361    }
362}
363
364impl std::error::Error for DecodeError {}
365
366/// Requests a Charging Station may send to the CSMS.
367#[derive(Debug, Clone, PartialEq)]
368pub enum StationCall {
369    Authorize(AuthorizeRequest),
370    BootNotification(BootNotificationRequest),
371    DataTransfer(DataTransferRequest),
372    DiagnosticsStatusNotification(DiagnosticsStatusNotificationRequest),
373    FirmwareStatusNotification(FirmwareStatusNotificationRequest),
374    Heartbeat(HeartbeatRequest),
375    MeterValues(MeterValuesRequest),
376    StartTransaction(StartTransactionRequest),
377    StatusNotification(StatusNotificationRequest),
378    StopTransaction(StopTransactionRequest),
379}
380
381impl StationCall {
382    pub const fn action(&self) -> Action {
383        match self {
384            StationCall::Authorize(_) => Action::Authorize,
385            StationCall::BootNotification(_) => Action::BootNotification,
386            StationCall::DataTransfer(_) => Action::DataTransfer,
387            StationCall::DiagnosticsStatusNotification(_) => Action::DiagnosticsStatusNotification,
388            StationCall::FirmwareStatusNotification(_) => Action::FirmwareStatusNotification,
389            StationCall::Heartbeat(_) => Action::Heartbeat,
390            StationCall::MeterValues(_) => Action::MeterValues,
391            StationCall::StartTransaction(_) => Action::StartTransaction,
392            StationCall::StatusNotification(_) => Action::StatusNotification,
393            StationCall::StopTransaction(_) => Action::StopTransaction,
394        }
395    }
396
397    /// Decodes and validates the payload of a CALL.
398    pub fn decode(action: &str, payload: &str) -> Result<Self, DecodeError> {
399        let a = Action::from_name(action)
400            .ok_or_else(|| DecodeError::UnknownAction(action.to_owned()))?;
401        match a {
402            Action::Authorize => ocpp_core::decode_payload(payload)
403                .map(StationCall::Authorize)
404                .map_err(DecodeError::Invalid),
405            Action::BootNotification => ocpp_core::decode_payload(payload)
406                .map(StationCall::BootNotification)
407                .map_err(DecodeError::Invalid),
408            Action::DataTransfer => ocpp_core::decode_payload(payload)
409                .map(StationCall::DataTransfer)
410                .map_err(DecodeError::Invalid),
411            Action::DiagnosticsStatusNotification => ocpp_core::decode_payload(payload)
412                .map(StationCall::DiagnosticsStatusNotification)
413                .map_err(DecodeError::Invalid),
414            Action::FirmwareStatusNotification => ocpp_core::decode_payload(payload)
415                .map(StationCall::FirmwareStatusNotification)
416                .map_err(DecodeError::Invalid),
417            Action::Heartbeat => ocpp_core::decode_payload(payload)
418                .map(StationCall::Heartbeat)
419                .map_err(DecodeError::Invalid),
420            Action::MeterValues => ocpp_core::decode_payload(payload)
421                .map(StationCall::MeterValues)
422                .map_err(DecodeError::Invalid),
423            Action::StartTransaction => ocpp_core::decode_payload(payload)
424                .map(StationCall::StartTransaction)
425                .map_err(DecodeError::Invalid),
426            Action::StatusNotification => ocpp_core::decode_payload(payload)
427                .map(StationCall::StatusNotification)
428                .map_err(DecodeError::Invalid),
429            Action::StopTransaction => ocpp_core::decode_payload(payload)
430                .map(StationCall::StopTransaction)
431                .map_err(DecodeError::Invalid),
432            #[allow(unreachable_patterns)]
433            other => Err(DecodeError::WrongDirection(other)),
434        }
435    }
436
437    /// Decodes and validates a request given as a JSON value (e.g. from the HTTP API).
438    pub fn from_value(action: &str, payload: serde_json::Value) -> Result<Self, DecodeError> {
439        let a = Action::from_name(action)
440            .ok_or_else(|| DecodeError::UnknownAction(action.to_owned()))?;
441        match a {
442            Action::Authorize => ocpp_core::decode_value(payload)
443                .map(StationCall::Authorize)
444                .map_err(DecodeError::Invalid),
445            Action::BootNotification => ocpp_core::decode_value(payload)
446                .map(StationCall::BootNotification)
447                .map_err(DecodeError::Invalid),
448            Action::DataTransfer => ocpp_core::decode_value(payload)
449                .map(StationCall::DataTransfer)
450                .map_err(DecodeError::Invalid),
451            Action::DiagnosticsStatusNotification => ocpp_core::decode_value(payload)
452                .map(StationCall::DiagnosticsStatusNotification)
453                .map_err(DecodeError::Invalid),
454            Action::FirmwareStatusNotification => ocpp_core::decode_value(payload)
455                .map(StationCall::FirmwareStatusNotification)
456                .map_err(DecodeError::Invalid),
457            Action::Heartbeat => ocpp_core::decode_value(payload)
458                .map(StationCall::Heartbeat)
459                .map_err(DecodeError::Invalid),
460            Action::MeterValues => ocpp_core::decode_value(payload)
461                .map(StationCall::MeterValues)
462                .map_err(DecodeError::Invalid),
463            Action::StartTransaction => ocpp_core::decode_value(payload)
464                .map(StationCall::StartTransaction)
465                .map_err(DecodeError::Invalid),
466            Action::StatusNotification => ocpp_core::decode_value(payload)
467                .map(StationCall::StatusNotification)
468                .map_err(DecodeError::Invalid),
469            Action::StopTransaction => ocpp_core::decode_value(payload)
470                .map(StationCall::StopTransaction)
471                .map_err(DecodeError::Invalid),
472            #[allow(unreachable_patterns)]
473            other => Err(DecodeError::WrongDirection(other)),
474        }
475    }
476
477    /// Serializes the request payload.
478    pub fn to_payload(&self) -> serde_json::Value {
479        match self {
480            StationCall::Authorize(r) => serde_json::to_value(r).expect("serializable"),
481            StationCall::BootNotification(r) => serde_json::to_value(r).expect("serializable"),
482            StationCall::DataTransfer(r) => serde_json::to_value(r).expect("serializable"),
483            StationCall::DiagnosticsStatusNotification(r) => {
484                serde_json::to_value(r).expect("serializable")
485            }
486            StationCall::FirmwareStatusNotification(r) => {
487                serde_json::to_value(r).expect("serializable")
488            }
489            StationCall::Heartbeat(r) => serde_json::to_value(r).expect("serializable"),
490            StationCall::MeterValues(r) => serde_json::to_value(r).expect("serializable"),
491            StationCall::StartTransaction(r) => serde_json::to_value(r).expect("serializable"),
492            StationCall::StatusNotification(r) => serde_json::to_value(r).expect("serializable"),
493            StationCall::StopTransaction(r) => serde_json::to_value(r).expect("serializable"),
494        }
495    }
496}
497
498/// Requests the CSMS may send to a Charging Station.
499#[derive(Debug, Clone, PartialEq)]
500pub enum CsmsCall {
501    CancelReservation(CancelReservationRequest),
502    ChangeAvailability(ChangeAvailabilityRequest),
503    ChangeConfiguration(ChangeConfigurationRequest),
504    ClearCache(ClearCacheRequest),
505    ClearChargingProfile(ClearChargingProfileRequest),
506    DataTransfer(DataTransferRequest),
507    GetCompositeSchedule(GetCompositeScheduleRequest),
508    GetConfiguration(GetConfigurationRequest),
509    GetDiagnostics(GetDiagnosticsRequest),
510    GetLocalListVersion(GetLocalListVersionRequest),
511    RemoteStartTransaction(RemoteStartTransactionRequest),
512    RemoteStopTransaction(RemoteStopTransactionRequest),
513    ReserveNow(ReserveNowRequest),
514    Reset(ResetRequest),
515    SendLocalList(SendLocalListRequest),
516    SetChargingProfile(SetChargingProfileRequest),
517    TriggerMessage(TriggerMessageRequest),
518    UnlockConnector(UnlockConnectorRequest),
519    UpdateFirmware(UpdateFirmwareRequest),
520}
521
522impl CsmsCall {
523    pub const fn action(&self) -> Action {
524        match self {
525            CsmsCall::CancelReservation(_) => Action::CancelReservation,
526            CsmsCall::ChangeAvailability(_) => Action::ChangeAvailability,
527            CsmsCall::ChangeConfiguration(_) => Action::ChangeConfiguration,
528            CsmsCall::ClearCache(_) => Action::ClearCache,
529            CsmsCall::ClearChargingProfile(_) => Action::ClearChargingProfile,
530            CsmsCall::DataTransfer(_) => Action::DataTransfer,
531            CsmsCall::GetCompositeSchedule(_) => Action::GetCompositeSchedule,
532            CsmsCall::GetConfiguration(_) => Action::GetConfiguration,
533            CsmsCall::GetDiagnostics(_) => Action::GetDiagnostics,
534            CsmsCall::GetLocalListVersion(_) => Action::GetLocalListVersion,
535            CsmsCall::RemoteStartTransaction(_) => Action::RemoteStartTransaction,
536            CsmsCall::RemoteStopTransaction(_) => Action::RemoteStopTransaction,
537            CsmsCall::ReserveNow(_) => Action::ReserveNow,
538            CsmsCall::Reset(_) => Action::Reset,
539            CsmsCall::SendLocalList(_) => Action::SendLocalList,
540            CsmsCall::SetChargingProfile(_) => Action::SetChargingProfile,
541            CsmsCall::TriggerMessage(_) => Action::TriggerMessage,
542            CsmsCall::UnlockConnector(_) => Action::UnlockConnector,
543            CsmsCall::UpdateFirmware(_) => Action::UpdateFirmware,
544        }
545    }
546
547    /// Decodes and validates the payload of a CALL.
548    pub fn decode(action: &str, payload: &str) -> Result<Self, DecodeError> {
549        let a = Action::from_name(action)
550            .ok_or_else(|| DecodeError::UnknownAction(action.to_owned()))?;
551        match a {
552            Action::CancelReservation => ocpp_core::decode_payload(payload)
553                .map(CsmsCall::CancelReservation)
554                .map_err(DecodeError::Invalid),
555            Action::ChangeAvailability => ocpp_core::decode_payload(payload)
556                .map(CsmsCall::ChangeAvailability)
557                .map_err(DecodeError::Invalid),
558            Action::ChangeConfiguration => ocpp_core::decode_payload(payload)
559                .map(CsmsCall::ChangeConfiguration)
560                .map_err(DecodeError::Invalid),
561            Action::ClearCache => ocpp_core::decode_payload(payload)
562                .map(CsmsCall::ClearCache)
563                .map_err(DecodeError::Invalid),
564            Action::ClearChargingProfile => ocpp_core::decode_payload(payload)
565                .map(CsmsCall::ClearChargingProfile)
566                .map_err(DecodeError::Invalid),
567            Action::DataTransfer => ocpp_core::decode_payload(payload)
568                .map(CsmsCall::DataTransfer)
569                .map_err(DecodeError::Invalid),
570            Action::GetCompositeSchedule => ocpp_core::decode_payload(payload)
571                .map(CsmsCall::GetCompositeSchedule)
572                .map_err(DecodeError::Invalid),
573            Action::GetConfiguration => ocpp_core::decode_payload(payload)
574                .map(CsmsCall::GetConfiguration)
575                .map_err(DecodeError::Invalid),
576            Action::GetDiagnostics => ocpp_core::decode_payload(payload)
577                .map(CsmsCall::GetDiagnostics)
578                .map_err(DecodeError::Invalid),
579            Action::GetLocalListVersion => ocpp_core::decode_payload(payload)
580                .map(CsmsCall::GetLocalListVersion)
581                .map_err(DecodeError::Invalid),
582            Action::RemoteStartTransaction => ocpp_core::decode_payload(payload)
583                .map(CsmsCall::RemoteStartTransaction)
584                .map_err(DecodeError::Invalid),
585            Action::RemoteStopTransaction => ocpp_core::decode_payload(payload)
586                .map(CsmsCall::RemoteStopTransaction)
587                .map_err(DecodeError::Invalid),
588            Action::ReserveNow => ocpp_core::decode_payload(payload)
589                .map(CsmsCall::ReserveNow)
590                .map_err(DecodeError::Invalid),
591            Action::Reset => ocpp_core::decode_payload(payload)
592                .map(CsmsCall::Reset)
593                .map_err(DecodeError::Invalid),
594            Action::SendLocalList => ocpp_core::decode_payload(payload)
595                .map(CsmsCall::SendLocalList)
596                .map_err(DecodeError::Invalid),
597            Action::SetChargingProfile => ocpp_core::decode_payload(payload)
598                .map(CsmsCall::SetChargingProfile)
599                .map_err(DecodeError::Invalid),
600            Action::TriggerMessage => ocpp_core::decode_payload(payload)
601                .map(CsmsCall::TriggerMessage)
602                .map_err(DecodeError::Invalid),
603            Action::UnlockConnector => ocpp_core::decode_payload(payload)
604                .map(CsmsCall::UnlockConnector)
605                .map_err(DecodeError::Invalid),
606            Action::UpdateFirmware => ocpp_core::decode_payload(payload)
607                .map(CsmsCall::UpdateFirmware)
608                .map_err(DecodeError::Invalid),
609            #[allow(unreachable_patterns)]
610            other => Err(DecodeError::WrongDirection(other)),
611        }
612    }
613
614    /// Decodes and validates a request given as a JSON value (e.g. from the HTTP API).
615    pub fn from_value(action: &str, payload: serde_json::Value) -> Result<Self, DecodeError> {
616        let a = Action::from_name(action)
617            .ok_or_else(|| DecodeError::UnknownAction(action.to_owned()))?;
618        match a {
619            Action::CancelReservation => ocpp_core::decode_value(payload)
620                .map(CsmsCall::CancelReservation)
621                .map_err(DecodeError::Invalid),
622            Action::ChangeAvailability => ocpp_core::decode_value(payload)
623                .map(CsmsCall::ChangeAvailability)
624                .map_err(DecodeError::Invalid),
625            Action::ChangeConfiguration => ocpp_core::decode_value(payload)
626                .map(CsmsCall::ChangeConfiguration)
627                .map_err(DecodeError::Invalid),
628            Action::ClearCache => ocpp_core::decode_value(payload)
629                .map(CsmsCall::ClearCache)
630                .map_err(DecodeError::Invalid),
631            Action::ClearChargingProfile => ocpp_core::decode_value(payload)
632                .map(CsmsCall::ClearChargingProfile)
633                .map_err(DecodeError::Invalid),
634            Action::DataTransfer => ocpp_core::decode_value(payload)
635                .map(CsmsCall::DataTransfer)
636                .map_err(DecodeError::Invalid),
637            Action::GetCompositeSchedule => ocpp_core::decode_value(payload)
638                .map(CsmsCall::GetCompositeSchedule)
639                .map_err(DecodeError::Invalid),
640            Action::GetConfiguration => ocpp_core::decode_value(payload)
641                .map(CsmsCall::GetConfiguration)
642                .map_err(DecodeError::Invalid),
643            Action::GetDiagnostics => ocpp_core::decode_value(payload)
644                .map(CsmsCall::GetDiagnostics)
645                .map_err(DecodeError::Invalid),
646            Action::GetLocalListVersion => ocpp_core::decode_value(payload)
647                .map(CsmsCall::GetLocalListVersion)
648                .map_err(DecodeError::Invalid),
649            Action::RemoteStartTransaction => ocpp_core::decode_value(payload)
650                .map(CsmsCall::RemoteStartTransaction)
651                .map_err(DecodeError::Invalid),
652            Action::RemoteStopTransaction => ocpp_core::decode_value(payload)
653                .map(CsmsCall::RemoteStopTransaction)
654                .map_err(DecodeError::Invalid),
655            Action::ReserveNow => ocpp_core::decode_value(payload)
656                .map(CsmsCall::ReserveNow)
657                .map_err(DecodeError::Invalid),
658            Action::Reset => ocpp_core::decode_value(payload)
659                .map(CsmsCall::Reset)
660                .map_err(DecodeError::Invalid),
661            Action::SendLocalList => ocpp_core::decode_value(payload)
662                .map(CsmsCall::SendLocalList)
663                .map_err(DecodeError::Invalid),
664            Action::SetChargingProfile => ocpp_core::decode_value(payload)
665                .map(CsmsCall::SetChargingProfile)
666                .map_err(DecodeError::Invalid),
667            Action::TriggerMessage => ocpp_core::decode_value(payload)
668                .map(CsmsCall::TriggerMessage)
669                .map_err(DecodeError::Invalid),
670            Action::UnlockConnector => ocpp_core::decode_value(payload)
671                .map(CsmsCall::UnlockConnector)
672                .map_err(DecodeError::Invalid),
673            Action::UpdateFirmware => ocpp_core::decode_value(payload)
674                .map(CsmsCall::UpdateFirmware)
675                .map_err(DecodeError::Invalid),
676            #[allow(unreachable_patterns)]
677            other => Err(DecodeError::WrongDirection(other)),
678        }
679    }
680
681    /// Serializes the request payload.
682    pub fn to_payload(&self) -> serde_json::Value {
683        match self {
684            CsmsCall::CancelReservation(r) => serde_json::to_value(r).expect("serializable"),
685            CsmsCall::ChangeAvailability(r) => serde_json::to_value(r).expect("serializable"),
686            CsmsCall::ChangeConfiguration(r) => serde_json::to_value(r).expect("serializable"),
687            CsmsCall::ClearCache(r) => serde_json::to_value(r).expect("serializable"),
688            CsmsCall::ClearChargingProfile(r) => serde_json::to_value(r).expect("serializable"),
689            CsmsCall::DataTransfer(r) => serde_json::to_value(r).expect("serializable"),
690            CsmsCall::GetCompositeSchedule(r) => serde_json::to_value(r).expect("serializable"),
691            CsmsCall::GetConfiguration(r) => serde_json::to_value(r).expect("serializable"),
692            CsmsCall::GetDiagnostics(r) => serde_json::to_value(r).expect("serializable"),
693            CsmsCall::GetLocalListVersion(r) => serde_json::to_value(r).expect("serializable"),
694            CsmsCall::RemoteStartTransaction(r) => serde_json::to_value(r).expect("serializable"),
695            CsmsCall::RemoteStopTransaction(r) => serde_json::to_value(r).expect("serializable"),
696            CsmsCall::ReserveNow(r) => serde_json::to_value(r).expect("serializable"),
697            CsmsCall::Reset(r) => serde_json::to_value(r).expect("serializable"),
698            CsmsCall::SendLocalList(r) => serde_json::to_value(r).expect("serializable"),
699            CsmsCall::SetChargingProfile(r) => serde_json::to_value(r).expect("serializable"),
700            CsmsCall::TriggerMessage(r) => serde_json::to_value(r).expect("serializable"),
701            CsmsCall::UnlockConnector(r) => serde_json::to_value(r).expect("serializable"),
702            CsmsCall::UpdateFirmware(r) => serde_json::to_value(r).expect("serializable"),
703        }
704    }
705}
706
707/// Decodes and validates the CALLRESULT payload for `action`, returning it as JSON.
708pub fn validate_response(action: Action, payload: &str) -> Result<serde_json::Value, Violation> {
709    match action {
710        Action::Authorize => ocpp_core::decode_payload::<AuthorizeResponse>(payload)
711            .map(|r| serde_json::to_value(r).expect("serializable")),
712        Action::BootNotification => ocpp_core::decode_payload::<BootNotificationResponse>(payload)
713            .map(|r| serde_json::to_value(r).expect("serializable")),
714        Action::CancelReservation => {
715            ocpp_core::decode_payload::<CancelReservationResponse>(payload)
716                .map(|r| serde_json::to_value(r).expect("serializable"))
717        }
718        Action::ChangeAvailability => {
719            ocpp_core::decode_payload::<ChangeAvailabilityResponse>(payload)
720                .map(|r| serde_json::to_value(r).expect("serializable"))
721        }
722        Action::ChangeConfiguration => {
723            ocpp_core::decode_payload::<ChangeConfigurationResponse>(payload)
724                .map(|r| serde_json::to_value(r).expect("serializable"))
725        }
726        Action::ClearCache => ocpp_core::decode_payload::<ClearCacheResponse>(payload)
727            .map(|r| serde_json::to_value(r).expect("serializable")),
728        Action::ClearChargingProfile => {
729            ocpp_core::decode_payload::<ClearChargingProfileResponse>(payload)
730                .map(|r| serde_json::to_value(r).expect("serializable"))
731        }
732        Action::DataTransfer => ocpp_core::decode_payload::<DataTransferResponse>(payload)
733            .map(|r| serde_json::to_value(r).expect("serializable")),
734        Action::DiagnosticsStatusNotification => {
735            ocpp_core::decode_payload::<DiagnosticsStatusNotificationResponse>(payload)
736                .map(|r| serde_json::to_value(r).expect("serializable"))
737        }
738        Action::FirmwareStatusNotification => {
739            ocpp_core::decode_payload::<FirmwareStatusNotificationResponse>(payload)
740                .map(|r| serde_json::to_value(r).expect("serializable"))
741        }
742        Action::GetCompositeSchedule => {
743            ocpp_core::decode_payload::<GetCompositeScheduleResponse>(payload)
744                .map(|r| serde_json::to_value(r).expect("serializable"))
745        }
746        Action::GetConfiguration => ocpp_core::decode_payload::<GetConfigurationResponse>(payload)
747            .map(|r| serde_json::to_value(r).expect("serializable")),
748        Action::GetDiagnostics => ocpp_core::decode_payload::<GetDiagnosticsResponse>(payload)
749            .map(|r| serde_json::to_value(r).expect("serializable")),
750        Action::GetLocalListVersion => {
751            ocpp_core::decode_payload::<GetLocalListVersionResponse>(payload)
752                .map(|r| serde_json::to_value(r).expect("serializable"))
753        }
754        Action::Heartbeat => ocpp_core::decode_payload::<HeartbeatResponse>(payload)
755            .map(|r| serde_json::to_value(r).expect("serializable")),
756        Action::MeterValues => ocpp_core::decode_payload::<MeterValuesResponse>(payload)
757            .map(|r| serde_json::to_value(r).expect("serializable")),
758        Action::RemoteStartTransaction => {
759            ocpp_core::decode_payload::<RemoteStartTransactionResponse>(payload)
760                .map(|r| serde_json::to_value(r).expect("serializable"))
761        }
762        Action::RemoteStopTransaction => {
763            ocpp_core::decode_payload::<RemoteStopTransactionResponse>(payload)
764                .map(|r| serde_json::to_value(r).expect("serializable"))
765        }
766        Action::ReserveNow => ocpp_core::decode_payload::<ReserveNowResponse>(payload)
767            .map(|r| serde_json::to_value(r).expect("serializable")),
768        Action::Reset => ocpp_core::decode_payload::<ResetResponse>(payload)
769            .map(|r| serde_json::to_value(r).expect("serializable")),
770        Action::SendLocalList => ocpp_core::decode_payload::<SendLocalListResponse>(payload)
771            .map(|r| serde_json::to_value(r).expect("serializable")),
772        Action::SetChargingProfile => {
773            ocpp_core::decode_payload::<SetChargingProfileResponse>(payload)
774                .map(|r| serde_json::to_value(r).expect("serializable"))
775        }
776        Action::StartTransaction => ocpp_core::decode_payload::<StartTransactionResponse>(payload)
777            .map(|r| serde_json::to_value(r).expect("serializable")),
778        Action::StatusNotification => {
779            ocpp_core::decode_payload::<StatusNotificationResponse>(payload)
780                .map(|r| serde_json::to_value(r).expect("serializable"))
781        }
782        Action::StopTransaction => ocpp_core::decode_payload::<StopTransactionResponse>(payload)
783            .map(|r| serde_json::to_value(r).expect("serializable")),
784        Action::TriggerMessage => ocpp_core::decode_payload::<TriggerMessageResponse>(payload)
785            .map(|r| serde_json::to_value(r).expect("serializable")),
786        Action::UnlockConnector => ocpp_core::decode_payload::<UnlockConnectorResponse>(payload)
787            .map(|r| serde_json::to_value(r).expect("serializable")),
788        Action::UpdateFirmware => ocpp_core::decode_payload::<UpdateFirmwareResponse>(payload)
789            .map(|r| serde_json::to_value(r).expect("serializable")),
790    }
791}