#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct NoCustomData;
#[cfg(feature = "serde")]
impl serde::Serialize for NoCustomData {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
serializer.serialize_map(Some(0))?.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for NoCustomData {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = NoCustomData;
fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("a customData object")
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
while map
.next_entry::<serde::de::IgnoredAny, serde::de::IgnoredAny>()?
.is_some()
{}
Ok(NoCustomData)
}
fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
Ok(NoCustomData)
}
}
deserializer.deserialize_map(Visitor)
}
}
#[cfg(all(test, feature = "serde"))]
mod tests {
use crate::Action;
use crate::v21::HeartbeatRequest;
use crate::v21::common::{Component, CustomData};
#[test]
fn the_default_discards_a_populated_custom_data_object() {
let json = r#"{"customData":{"vendorId":"com.acme","extra":[1,2,3]},"name":"EVSE"}"#;
let (component, _): (Component, _) = serde_json_core::from_str(json).unwrap();
assert_eq!(component.name.as_str(), "EVSE");
assert!(component.custom_data.is_some());
}
#[test]
fn the_default_still_accepts_a_message_without_custom_data() {
let (component, _): (Component, _) =
serde_json_core::from_str(r#"{"name":"EVSE"}"#).unwrap();
assert!(component.custom_data.is_none());
}
#[test]
fn a_caller_can_name_the_specs_custom_data_shape_and_read_the_vendor_id() {
let json = r#"{"customData":{"vendorId":"com.acme"},"name":"EVSE"}"#;
let (component, _): (Component<CustomData>, _) =
serde_json_core::from_str(json).unwrap();
assert_eq!(component.custom_data.unwrap().vendor_id.as_str(), "com.acme");
}
#[test]
fn a_caller_can_supply_an_entirely_custom_shape() {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
struct AcmeExtension {
#[serde(rename = "vendorId")]
vendor_id: heapless::String<16>,
#[serde(rename = "siteId")]
site_id: u32,
}
let json = r#"{"customData":{"vendorId":"com.acme","siteId":42},"name":"EVSE"}"#;
let (component, _): (Component<AcmeExtension>, _) =
serde_json_core::from_str(json).unwrap();
let extension = component.custom_data.unwrap();
assert_eq!(extension.site_id, 42);
assert_eq!(extension.vendor_id.as_str(), "com.acme");
}
#[test]
fn the_parameter_reaches_the_message_types() {
let request: HeartbeatRequest<CustomData> = HeartbeatRequest {
custom_data: Some(CustomData {
vendor_id: heapless::String::try_from("com.acme").unwrap(),
}),
};
assert_eq!(HeartbeatRequest::<CustomData>::ACTION, "Heartbeat");
assert!(request.custom_data.is_some());
}
#[test]
fn the_default_costs_one_byte_and_the_spec_shape_costs_the_full_field() {
assert_eq!(core::mem::size_of::<Option<super::NoCustomData>>(), 1);
assert_eq!(core::mem::size_of::<Option<CustomData>>(), 272);
}
}