use std::str::FromStr;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(
try_from = "String",
into = "String",
expecting = "a string like <1000|6|100042>"
)]
pub struct Entity {
pub pid: u32,
pub hid: u32,
pub uid: u64,
}
impl std::fmt::Debug for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl std::fmt::Display for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<{}|{}|{}>", self.pid, self.hid, self.uid)
}
}
#[derive(Error, Debug)]
#[error("Cannot parse entity from string: {0}")]
pub struct EntityParseError(String);
impl Entity {
pub const NULL: Self = Self::new(0, 0, 0);
pub const fn new(pid: u32, hid: u32, uid: u64) -> Self {
Self { pid, hid, uid }
}
pub fn is_null(&self) -> bool {
*self == Self::NULL
}
pub fn from_str_lenient(s: &str) -> Result<Self, EntityParseError> {
if !s.starts_with('<') || !s.ends_with('>') {
return Err(EntityParseError(s.to_string()));
}
let ids: Vec<_> = s.split('|').collect();
match ids.as_slice() {
[uid] => {
let uid = u64::from_str(uid).map_err(|_| EntityParseError(s.to_string()))?;
Ok(Entity {
pid: 0,
hid: 0,
uid,
})
}
[pid, hid, uid] => {
let pid = u32::from_str(pid).map_err(|_| EntityParseError(s.to_string()))?;
let hid = u32::from_str(hid).map_err(|_| EntityParseError(s.to_string()))?;
let uid = u64::from_str(uid).map_err(|_| EntityParseError(s.to_string()))?;
Ok(Entity { pid, hid, uid })
}
_ => Err(EntityParseError(s.to_string())),
}
}
pub fn from_str_strict(s: &str) -> Result<Self, EntityParseError> {
if !s.starts_with('<') || !s.ends_with('>') {
return Err(EntityParseError(s.to_string()));
}
let ids: Vec<_> = s[1..s.len() - 1].split('|').collect();
match ids.as_slice() {
[pid, hid, uid] => {
let pid = u32::from_str(pid).map_err(|_| EntityParseError(s.to_string()))?;
let hid = u32::from_str(hid).map_err(|_| EntityParseError(s.to_string()))?;
let uid = u64::from_str(uid).map_err(|_| EntityParseError(s.to_string()))?;
Ok(Entity { pid, hid, uid })
}
_ => Err(EntityParseError(s.to_string())),
}
}
}
impl FromStr for Entity {
type Err = EntityParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str_strict(s)
}
}
impl TryFrom<String> for Entity {
type Error = EntityParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::from_str_strict(&s)
}
}
impl From<Entity> for String {
fn from(val: Entity) -> Self {
val.to_string()
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use super::*;
#[test]
fn parse_string() {
let e: Entity = "<1000|6|100384>".parse().unwrap();
assert_eq!(e, Entity::new(1000, 6, 100384));
}
#[test]
fn parse_json() {
let json = json!("<1002|27|13>");
let e: Entity = serde_json::from_value(json).unwrap();
assert_eq!(e, Entity::new(1002, 27, 13));
}
}