use serde::{Deserialize, Serialize};
macro_rules! define_copy_id {
(
$(#[$meta:meta])*
$name:ident($inner:ty)
) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name($inner);
impl $name {
#[inline]
#[must_use]
pub const fn new(value: $inner) -> Self {
Self(value)
}
#[inline]
#[must_use]
pub const fn as_inner(&self) -> &$inner {
&self.0
}
#[inline]
#[must_use]
pub const fn into_inner(self) -> $inner {
self.0
}
}
impl core::fmt::Display for $name {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
impl From<$inner> for $name {
#[inline]
fn from(value: $inner) -> Self {
Self(value)
}
}
};
}
macro_rules! define_string_id {
(
$(#[$meta:meta])*
$name:ident
) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(String);
impl $name {
#[inline]
#[must_use]
pub const fn new(value: String) -> Self {
Self(value)
}
#[inline]
#[must_use]
pub fn as_inner(&self) -> &str {
&self.0
}
#[inline]
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
}
impl core::fmt::Display for $name {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
impl From<String> for $name {
#[inline]
fn from(value: String) -> Self {
Self(value)
}
}
};
}
define_copy_id! {
UserId(i64)
}
define_copy_id! {
InstrumentId(i32)
}
define_copy_id! {
CompanyId(i32)
}
define_string_id! {
AccountId
}
define_string_id! {
TagId
}
define_string_id! {
MerchantId
}
define_string_id! {
ReminderId
}
define_string_id! {
ReminderMarkerId
}
define_string_id! {
TransactionId
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn user_id_serde_roundtrip() {
let id = UserId::new(42);
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "42");
let deserialized: UserId = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, id);
}
#[test]
fn instrument_id_serde_roundtrip() {
let id = InstrumentId::new(1);
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "1");
let deserialized: InstrumentId = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, id);
}
#[test]
fn company_id_serde_roundtrip() {
let id = CompanyId::new(100);
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "100");
let deserialized: CompanyId = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, id);
}
#[test]
fn account_id_serde_roundtrip() {
let id = AccountId::new("550e8400-e29b-41d4-a716-446655440000".to_owned());
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, r#""550e8400-e29b-41d4-a716-446655440000""#);
let deserialized: AccountId = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, id);
}
#[test]
fn tag_id_serde_roundtrip() {
let id = TagId::new("a1b2c3d4-0000-0000-0000-000000000000".to_owned());
let json = serde_json::to_string(&id).unwrap();
let deserialized: TagId = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, id);
}
#[test]
fn string_id_display() {
let id = AccountId::new("abc-123".to_owned());
assert_eq!(id.to_string(), "abc-123");
}
#[test]
fn numeric_id_display() {
let id = UserId::new(99);
assert_eq!(id.to_string(), "99");
}
#[test]
fn id_from_inner() {
let id: UserId = 42_i64.into();
assert_eq!(*id.as_inner(), 42);
let id: AccountId = "abc".to_owned().into();
assert_eq!(id.as_inner(), "abc");
}
#[test]
fn id_into_inner() {
let id = UserId::new(7);
assert_eq!(id.into_inner(), 7);
let id = MerchantId::new("m-1".to_owned());
assert_eq!(id.into_inner(), "m-1");
}
#[test]
fn copy_id_is_copy() {
let id = UserId::new(1);
let id2 = id;
assert_eq!(id, id2);
}
#[test]
fn different_id_types_are_distinct() {
let _user = UserId::new(1);
let _instrument = InstrumentId::new(1);
let _company = CompanyId::new(1);
}
}