use serde::Serialize;
use serde_json::value::RawValue;
use crate::{ErrorCode, ErrorDialect, RpcError};
pub const MAX_MESSAGE_ID_LEN: usize = 36;
pub const MAX_ERROR_DESCRIPTION_LEN: usize = 255;
pub const UNKNOWN_MESSAGE_ID: &str = "-1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum MessageTypeId {
Call = 2,
CallResult = 3,
CallError = 4,
CallResultError = 5,
Send = 6,
}
#[derive(Debug)]
pub enum Frame {
Call {
id: String,
action: String,
payload: Box<RawValue>,
},
CallResult {
id: String,
payload: Box<RawValue>,
},
CallError {
id: String,
code: String,
description: String,
details: Box<RawValue>,
},
CallResultError {
id: String,
code: String,
description: String,
details: Box<RawValue>,
},
Send {
id: String,
action: String,
payload: Box<RawValue>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum FrameError {
Malformed { id: Option<String>, reason: String },
UnknownMessageType { id: Option<String>, type_id: u64 },
}
impl FrameError {
pub fn reply_id(&self) -> &str {
match self {
FrameError::Malformed { id, .. } | FrameError::UnknownMessageType { id, .. } => {
id.as_deref().unwrap_or(UNKNOWN_MESSAGE_ID)
}
}
}
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Malformed { reason, .. } => write!(f, "malformed OCPP-J frame: {reason}"),
FrameError::UnknownMessageType { type_id, .. } => {
write!(f, "unknown message type id {type_id}")
}
}
}
}
impl std::error::Error for FrameError {}
fn empty_object() -> Box<RawValue> {
RawValue::from_string("{}".to_owned()).expect("valid json")
}
fn payload(raw: &RawValue) -> Box<RawValue> {
if raw.get() == "null" {
empty_object()
} else {
raw.to_owned()
}
}
fn string(raw: &RawValue) -> Option<String> {
serde_json::from_str::<String>(raw.get()).ok()
}
impl Frame {
pub fn parse(text: &str) -> Result<Frame, FrameError> {
let parts: Vec<&RawValue> =
serde_json::from_str(text).map_err(|e| FrameError::Malformed {
id: None,
reason: format!("not a JSON array: {e}"),
})?;
let type_id: u64 = parts
.first()
.and_then(|r| serde_json::from_str(r.get()).ok())
.ok_or_else(|| FrameError::Malformed {
id: None,
reason: "missing or invalid MessageTypeId".into(),
})?;
let id = parts.get(1).and_then(|r| string(r));
let Some(id_str) = id.clone() else {
return Err(FrameError::Malformed {
id: None,
reason: "missing or invalid MessageId".into(),
});
};
let bad = |reason: &str| FrameError::Malformed {
id: id.clone(),
reason: reason.into(),
};
let check_object = |r: &RawValue| -> Result<Box<RawValue>, FrameError> {
let p = payload(r);
if p.get().trim_start().starts_with('{') {
Ok(p)
} else {
Err(bad("payload is not a JSON object"))
}
};
match type_id {
2 | 6 => {
if parts.len() != 4 {
return Err(bad("CALL/SEND must have 4 elements"));
}
let action = string(parts[2]).ok_or_else(|| bad("Action is not a string"))?;
let payload = check_object(parts[3])?;
Ok(if type_id == 2 {
Frame::Call {
id: id_str,
action,
payload,
}
} else {
Frame::Send {
id: id_str,
action,
payload,
}
})
}
3 => {
if parts.len() != 3 {
return Err(bad("CALLRESULT must have 3 elements"));
}
Ok(Frame::CallResult {
id: id_str,
payload: check_object(parts[2])?,
})
}
4 | 5 => {
if !(4..=5).contains(&parts.len()) {
return Err(bad("CALLERROR must have 5 elements"));
}
let code = string(parts[2]).ok_or_else(|| bad("errorCode is not a string"))?;
let description = string(parts[3]).unwrap_or_default();
let details = parts
.get(4)
.map(|r| payload(r))
.unwrap_or_else(empty_object);
Ok(if type_id == 4 {
Frame::CallError {
id: id_str,
code,
description,
details,
}
} else {
Frame::CallResultError {
id: id_str,
code,
description,
details,
}
})
}
other => Err(FrameError::UnknownMessageType { id, type_id: other }),
}
}
pub fn id(&self) -> &str {
match self {
Frame::Call { id, .. }
| Frame::CallResult { id, .. }
| Frame::CallError { id, .. }
| Frame::CallResultError { id, .. }
| Frame::Send { id, .. } => id,
}
}
}
pub fn encode_call<P: Serialize + ?Sized>(
id: &str,
action: &str,
payload: &P,
) -> Result<String, serde_json::Error> {
serde_json::to_string(&(MessageTypeId::Call as u8, id, action, payload))
}
pub fn encode_send<P: Serialize + ?Sized>(
id: &str,
action: &str,
payload: &P,
) -> Result<String, serde_json::Error> {
serde_json::to_string(&(MessageTypeId::Send as u8, id, action, payload))
}
pub fn encode_result<P: Serialize + ?Sized>(
id: &str,
payload: &P,
) -> Result<String, serde_json::Error> {
serde_json::to_string(&(MessageTypeId::CallResult as u8, id, payload))
}
pub fn encode_error(id: &str, err: &RpcError, dialect: ErrorDialect, result_error: bool) -> String {
let type_id = if result_error {
MessageTypeId::CallResultError
} else {
MessageTypeId::CallError
} as u8;
let mut description = err.description.as_str();
if description.chars().count() > MAX_ERROR_DESCRIPTION_LEN {
let cut = description
.char_indices()
.nth(MAX_ERROR_DESCRIPTION_LEN)
.map(|(i, _)| i)
.unwrap_or(description.len());
description = &description[..cut];
}
let empty = serde_json::Value::Object(Default::default());
let details = if err.details.is_object() {
&err.details
} else {
&empty
};
serde_json::to_string(&(type_id, id, err.code.as_wire(dialect), description, details))
.expect("error frame serializes")
}
pub fn error_from_frame(code: &str, description: String, details: &RawValue) -> RpcError {
RpcError {
code: ErrorCode::from_wire(code),
description,
details: serde_json::from_str(details.get()).unwrap_or_default(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_call() {
let f = Frame::parse(r#"[2, "19223201", "BootNotification", {"chargePointVendor": "VendorX", "chargePointModel": "X"}]"#).unwrap();
match f {
Frame::Call {
id,
action,
payload,
} => {
assert_eq!(id, "19223201");
assert_eq!(action, "BootNotification");
assert!(payload.get().contains("VendorX"));
}
_ => panic!(),
}
let f = Frame::parse(r#"[2,"1","Heartbeat",null]"#).unwrap();
assert!(matches!(f, Frame::Call { ref payload, .. } if payload.get() == "{}"));
}
#[test]
fn parse_result_and_error() {
assert!(matches!(
Frame::parse(r#"[3,"a",{"status":"Accepted"}]"#).unwrap(),
Frame::CallResult { .. }
));
match Frame::parse(
r#"[4,"162376037","NotSupported","SetDisplayMessageRequest not supported",{}]"#,
)
.unwrap()
{
Frame::CallError {
code, description, ..
} => {
assert_eq!(code, "NotSupported");
assert_eq!(description, "SetDisplayMessageRequest not supported");
}
_ => panic!(),
}
assert!(matches!(
Frame::parse(r#"[5,"a","FormatViolation","",{}]"#).unwrap(),
Frame::CallResultError { .. }
));
assert!(matches!(
Frame::parse(r#"[6,"a","NotifyPeriodicEventStream",{}]"#).unwrap(),
Frame::Send { .. }
));
}
#[test]
fn parse_errors() {
assert_eq!(Frame::parse("garbage").unwrap_err().reply_id(), "-1");
assert_eq!(Frame::parse("[2]").unwrap_err().reply_id(), "-1");
assert_eq!(
Frame::parse(r#"[2,"abc","X"]"#).unwrap_err().reply_id(),
"abc"
);
assert_eq!(
Frame::parse(r#"[2,"abc","X",[]]"#).unwrap_err().reply_id(),
"abc"
);
assert_eq!(
Frame::parse(r#"[9,"abc"]"#).unwrap_err(),
FrameError::UnknownMessageType {
id: Some("abc".into()),
type_id: 9
}
);
}
#[test]
fn encode() {
assert_eq!(
encode_call("1", "Heartbeat", &serde_json::json!({})).unwrap(),
r#"[2,"1","Heartbeat",{}]"#
);
assert_eq!(
encode_result("1", &serde_json::json!({"a":1})).unwrap(),
r#"[3,"1",{"a":1}]"#
);
let e = RpcError::new(ErrorCode::FormatViolation, "bad");
assert_eq!(
encode_error("1", &e, ErrorDialect::Ocpp16, false),
r#"[4,"1","FormationViolation","bad",{}]"#
);
assert_eq!(
encode_error("1", &e, ErrorDialect::Ocpp2, true),
r#"[5,"1","FormatViolation","bad",{}]"#
);
let long = RpcError::new(ErrorCode::GenericError, "x".repeat(400));
let s = encode_error("1", &long, ErrorDialect::Ocpp2, false);
assert!(s.len() < 300);
}
}