use arcstr::ArcStr;
use compact_str::CompactString;
use nanoid::nanoid;
use std::{fmt::{self, Display}, ops::Deref};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SId(pub CompactString);
impl SId {
#[inline]
pub fn new() -> Self {
Self(CompactString::new(nanoid!(14)))
}
#[inline]
pub fn from_str(s: impl AsRef<str>) -> Self {
Self(CompactString::new(s))
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn is_inline(&self) -> bool {
!self.0.is_heap_allocated()
}
pub fn memory_info(&self) -> String {
format!(
"len={}, inline={}, heap={}",
self.len(),
self.is_inline(),
if self.is_inline() { 0 } else { self.len() }
)
}
}
impl Default for SId {
fn default() -> Self {
Self::new()
}
}
impl Display for SId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Deref for SId {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for SId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<&str> for SId {
fn from(s: &str) -> Self {
Self::from_str(s)
}
}
impl From<String> for SId {
fn from(s: String) -> Self {
Self(CompactString::from(s))
}
}
impl From<CompactString> for SId {
fn from(s: CompactString) -> Self {
Self(s)
}
}
impl From<&String> for SId {
fn from(s: &String) -> Self {
Self::from_str(s)
}
}
impl From<ArcStr> for SId {
fn from(value: ArcStr) -> Self {
Self::from_str(&value)
}
}
impl From<&ArcStr> for SId {
fn from(value: &ArcStr) -> Self {
Self::from_str(&value)
}
}
impl From<&SId> for SId {
fn from(value: &SId) -> Self {
value.clone()
}
}
impl Serialize for SId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for SId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{self, Visitor};
struct SIdVisitor;
impl<'de> Visitor<'de> for SIdVisitor {
type Value = SId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string or bytes representing an SId")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(SId::from_str(v))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(SId::from(v))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let s = std::str::from_utf8(v)
.map_err(|_| de::Error::custom("invalid UTF-8 in SId bytes"))?;
Ok(SId::from_str(s))
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: de::Error,
{
let s = String::from_utf8(v)
.map_err(|e| de::Error::custom(format!("invalid UTF-8 in SId: {}", e)))?;
Ok(SId::from(s))
}
}
if deserializer.is_human_readable() {
deserializer.deserialize_str(SIdVisitor)
} else {
let s = String::deserialize(deserializer)?;
Ok(SId::from(s))
}
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use serde::Serialize;
use crate::model::SId;
#[test]
fn default() {
let id = SId::default();
assert_eq!(id.len(), 14);
}
#[test]
fn from_string() {
let id = SId::from("hellothere");
assert_eq!(id.to_string(), "hellothere");
assert_eq!(id.as_ref(), "hellothere");
let an = SId::from(&id);
assert_eq!(id.to_string(), an.to_string());
assert!(id == an);
}
#[test]
fn cloned() {
let id = SId::new();
let cl = id.clone();
assert_eq!(cl, id);
let an = SId::new();
assert_ne!(cl, an);
}
#[test]
fn to_bytes() {
let id = SId::default();
let by = Bytes::from(id.to_string());
assert_eq!(id.as_ref(), &by);
}
#[test]
fn test_deserialize_new_format() {
let id = SId::from_str("test_id_123");
let serialized = bincode::serialize(&id).unwrap();
let deserialized: SId = bincode::deserialize(&serialized).unwrap();
assert_eq!(id, deserialized);
}
#[test]
fn test_deserialize_old_bytes_format() {
#[derive(Serialize)]
struct OldSId(Bytes);
let old_id = OldSId(Bytes::from("old_test_id"));
let serialized = bincode::serialize(&old_id).unwrap();
let deserialized: SId = bincode::deserialize(&serialized).unwrap();
assert_eq!(deserialized.as_str(), "old_test_id");
}
#[test]
fn test_roundtrip() {
let original = SId::from_str("roundtrip_test");
let serialized = bincode::serialize(&original).unwrap();
let deserialized: SId = bincode::deserialize(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_inline_optimization() {
let id = SId::new(); assert!(id.is_inline());
let serialized = bincode::serialize(&id).unwrap();
let deserialized: SId = bincode::deserialize(&serialized).unwrap();
assert!(deserialized.is_inline());
}
}