Skip to main content

ocpp_client/ocpp_1_6/
error.rs

1use crate::error::ProtocolError;
2use alloc::format;
3use alloc::string::{String, ToString};
4use core::fmt;
5use ocpp_types::v16::RpcErrorCode;
6use serde_json::{Value, json};
7
8/// An OCPP 1.6 CALLERROR: the RPC framework error code from
9/// [`ocpp_types::v16::RpcErrorCode`], paired with the free-text description and details a
10/// CALLERROR frame carries alongside it (the spec leaves `errorDetails`'s shape undefined, so
11/// this crate keeps it as a raw `Value` rather than a typed field).
12#[derive(Debug, Clone)]
13pub struct OCPP1_6Error {
14    pub code: RpcErrorCode,
15    pub description: String,
16    pub details: Value,
17}
18
19/// The exact wire spelling for each code, per Table 7 of the OCPP-J 1.6 specification -
20/// `RpcErrorCode`'s serde impl already produces this, but `ProtocolError::code` returns a
21/// borrowed `&str` rather than allocating, so this mirrors it as a `match` instead of going
22/// through serialization.
23fn wire_code(code: RpcErrorCode) -> &'static str {
24    match code {
25        RpcErrorCode::NotImplemented => "NotImplemented",
26        RpcErrorCode::NotSupported => "NotSupported",
27        RpcErrorCode::InternalError => "InternalError",
28        RpcErrorCode::ProtocolError => "ProtocolError",
29        RpcErrorCode::SecurityError => "SecurityError",
30        RpcErrorCode::FormationViolation => "FormationViolation",
31        RpcErrorCode::PropertyConstraintViolation => "PropertyConstraintViolation",
32        RpcErrorCode::OccurenceConstraintViolation => "OccurenceConstraintViolation",
33        RpcErrorCode::TypeConstraintViolation => "TypeConstraintViolation",
34        RpcErrorCode::GenericError => "GenericError",
35    }
36}
37
38impl ProtocolError for OCPP1_6Error {
39    fn code(&self) -> &str {
40        wire_code(self.code)
41    }
42
43    fn description(&self) -> &str {
44        &self.description
45    }
46
47    fn details(&self) -> &Value {
48        &self.details
49    }
50
51    fn not_implemented(action: &str) -> Self {
52        OCPP1_6Error {
53            code: RpcErrorCode::NotImplemented,
54            description: format!("Action '{action}' is not implemented"),
55            details: json!({}),
56        }
57    }
58
59    fn from_wire(code: &str, description: &str, details: Value) -> Self {
60        let code = match code {
61            "NotImplemented" => RpcErrorCode::NotImplemented,
62            "NotSupported" => RpcErrorCode::NotSupported,
63            "InternalError" => RpcErrorCode::InternalError,
64            "ProtocolError" => RpcErrorCode::ProtocolError,
65            "SecurityError" => RpcErrorCode::SecurityError,
66            "FormationViolation" => RpcErrorCode::FormationViolation,
67            "PropertyConstraintViolation" => RpcErrorCode::PropertyConstraintViolation,
68            "OccurenceConstraintViolation" => RpcErrorCode::OccurenceConstraintViolation,
69            "TypeConstraintViolation" => RpcErrorCode::TypeConstraintViolation,
70            _ => RpcErrorCode::GenericError,
71        };
72        OCPP1_6Error {
73            code,
74            description: description.to_string(),
75            details,
76        }
77    }
78}
79
80/// Answer a schema violation with the CALLERROR 1.6J names for it.
81///
82/// Note the spelling: 1.6J's RPC error table really does say `Occurence`, with one `r`, where
83/// 2.0.1 and 2.1 say `Occurrence`. That single-letter split across versions is why this
84/// conversion lives in this crate at all - `ocpp-types` classifies the violation but leaves the
85/// wire code to "the caller's version", and there are three of those.
86#[cfg(feature = "validate")]
87impl From<ocpp_types::validate::ValidationError> for OCPP1_6Error {
88    fn from(error: ocpp_types::validate::ValidationError) -> Self {
89        use ocpp_types::validate::ConstraintClass;
90
91        let code = match error.kind().constraint_class() {
92            ConstraintClass::Property => RpcErrorCode::PropertyConstraintViolation,
93            ConstraintClass::Occurrence => RpcErrorCode::OccurenceConstraintViolation,
94        };
95        let (description, details) = crate::error::validation_error_parts(&error);
96        OCPP1_6Error {
97            code,
98            description,
99            details,
100        }
101    }
102}
103
104impl fmt::Display for OCPP1_6Error {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "{}: {}", self.code(), self.description())
107    }
108}
109
110impl core::error::Error for OCPP1_6Error {}