use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MeterId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct GroupId(pub u64);
impl fmt::Display for MeterId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Display for GroupId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NodeId {
Group(GroupId),
Meter(MeterId),
}
const GROUP_PREFIX: &str = "GROUP";
const METER_PREFIX: &str = "MP";
impl NodeId {
pub const fn group(self) -> Option<GroupId> {
match self {
Self::Group(id) => Some(id),
Self::Meter(_) => None,
}
}
pub const fn meter(self) -> Option<MeterId> {
match self {
Self::Meter(id) => Some(id),
Self::Group(_) => None,
}
}
}
impl From<GroupId> for NodeId {
fn from(id: GroupId) -> Self {
Self::Group(id)
}
}
impl From<MeterId> for NodeId {
fn from(id: MeterId) -> Self {
Self::Meter(id)
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Group(id) => write!(f, "{GROUP_PREFIX}:{id}"),
Self::Meter(id) => write!(f, "{METER_PREFIX}:{id}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("invalid node id {0:?}")]
pub struct InvalidNodeId(pub String);
impl FromStr for NodeId {
type Err = InvalidNodeId;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let invalid = || InvalidNodeId(s.to_owned());
let (prefix, number) = s.split_once(':').ok_or_else(invalid)?;
if !number.bytes().all(|b| b.is_ascii_digit()) {
return Err(invalid());
}
let number = number.parse().map_err(|_| invalid())?;
match prefix {
GROUP_PREFIX => Ok(Self::Group(GroupId(number))),
METER_PREFIX => Ok(Self::Meter(MeterId(number))),
_ => Err(invalid()),
}
}
}
impl Serialize for NodeId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for NodeId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Cow::<'de, str>::deserialize(deserializer)?
.parse()
.map_err(D::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_both_kinds() {
for (text, node) in [
("MP:692294", NodeId::Meter(MeterId(692294))),
("GROUP:121497", NodeId::Group(GroupId(121497))),
] {
assert_eq!(text.parse::<NodeId>().unwrap(), node);
assert_eq!(node.to_string(), text);
let json = serde_json::to_string(&node).unwrap();
assert_eq!(json, format!("{text:?}"));
assert_eq!(serde_json::from_str::<NodeId>(&json).unwrap(), node);
}
}
#[test]
fn rejects_other_forms() {
for text in [
"692294", "MP:", "MP:x", "MP:+1", "MP:-1", "mp:1", "SITE:1", "#", "",
] {
assert_eq!(
text.parse::<NodeId>(),
Err(InvalidNodeId(text.into())),
"{text}"
);
}
}
#[test]
fn plain_ids_serialize_as_numbers() {
assert_eq!(serde_json::to_string(&MeterId(5)).unwrap(), "5");
assert_eq!(serde_json::to_string(&GroupId(7)).unwrap(), "7");
}
}