use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize};
macro_rules! add_id {
(
$(#[$meta:meta])*
$name:ident,
$prefix:expr
) => {
#[doc = concat!("An ID of a Resonite ", stringify!($name), "(`", $prefix, "{id}`)")]
#[doc = concat!("use resonite::id::", stringify!($name), ";")]
#[doc = concat!("let id1 = ", stringify!($name), "::try_from(\"", $prefix, "totally-legit-id\").unwrap();")]
#[doc = concat!("let id2 = ", stringify!($name), "::try_from(\"", $prefix, "other-legit-id\").unwrap();")]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
#[serde(transparent)]
$(#[$meta])*
pub struct $name(String);
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl TryFrom<String> for $name {
type Error = &'static str;
fn try_from(v: String) -> Result<Self, Self::Error> {
if !v.starts_with($prefix) {
return Err(concat!("should start with `", $prefix , "`"));
}
Ok($name(v))
}
}
impl TryFrom<&'static str> for $name {
type Error = &'static str;
fn try_from(v: &'static str) -> Result<Self, Self::Error> {
Self::try_from(v.to_owned())
}
}
impl From<$name> for String {
fn from(id: $name) -> String {
id.0
}
}
impl From<$name> for Any {
fn from(id: $name) -> Any {
Any::$name(id)
}
}
impl<'de> serde::de::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct IdVisitor;
impl<'de> Visitor<'de> for IdVisitor {
type Value = $name;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter
.write_str(concat!("a string, ", stringify!($name), "that must start with `", $prefix, "`"))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
$name::try_from(v.to_owned()).map_err(|_| {
de::Error::invalid_value(
serde::de::Unexpected::Str(v),
&concat!("start with `", $prefix , "`"),
)
})
}
}
deserializer.deserialize_str(IdVisitor)
}
}
};
}
add_id!(User, "U-");
add_id!(Group, "G-");
add_id!(Session, "S-");
add_id!(Record, "R-");
add_id!(Machine, "M-");
add_id!(UserSession, "");
impl Session {
#[must_use]
pub fn is_custom_user(&self) -> bool { self.0.starts_with("S-U-") }
}
#[repr(u8)]
#[cfg_attr(
feature = "borsh",
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
)]
#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Any {
Group(Group) = 4,
Machine(Machine) = 3,
Record(Record) = 2,
Session(Session) = 1,
User(User) = 0,
UserSession(UserSession) = 5,
}
impl AsRef<str> for Any {
fn as_ref(&self) -> &str {
match self {
Self::User(v) => &v.0,
Self::Group(v) => &v.0,
Self::Session(v) => &v.0,
Self::Record(v) => &v.0,
Self::Machine(v) => &v.0,
Self::UserSession(v) => &v.0,
}
}
}
#[repr(u8)]
#[cfg_attr(
feature = "borsh",
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
)]
#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Owner {
Group(Group) = 2,
Machine(Machine) = 1,
User(User) = 0,
}
impl AsRef<str> for Owner {
fn as_ref(&self) -> &str {
match self {
Self::User(v) => &v.0,
Self::Group(v) => &v.0,
Self::Machine(v) => &v.0,
}
}
}
impl From<User> for Owner {
fn from(user: User) -> Self { Self::User(user) }
}
impl From<Group> for Owner {
fn from(group: Group) -> Self { Self::Group(group) }
}
impl From<Machine> for Owner {
fn from(machine: Machine) -> Self { Self::Machine(machine) }
}
impl From<Owner> for Any {
fn from(id: Owner) -> Self {
match id {
Owner::User(v) => Self::User(v),
Owner::Group(v) => Self::Group(v),
Owner::Machine(v) => Self::Machine(v),
}
}
}
#[cfg(test)]
#[test]
fn user_session_id() {
serde_json::from_str::<UserSession>(
"\"c982fd2c-5fb4-4a81-b213-6b644f894069\"",
)
.unwrap();
serde_json::from_str::<UserSession>("\"manipulated-str\"").unwrap();
}