use std::{fmt, fmt::Write, str::FromStr};
use arrayvec::ArrayString;
use js_sys::JsString;
use serde::{
de::{Error, Visitor},
Deserialize, Serialize,
};
use super::errors::RawObjectIdParseError;
const MAX_PACKED_VAL: u128 = (1 << (32 * 3)) - 1;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RawObjectId {
packed: u128,
}
impl fmt::Debug for RawObjectId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawObjectId")
.field("packed", &self.packed)
.field("real", &self.to_string())
.finish()
}
}
impl fmt::Display for RawObjectId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:0width$x}",
self.packed >> 32,
width = (self.packed as u32) as usize
)
}
}
impl Serialize for RawObjectId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.collect_str(&self.to_array_string())
} else {
serializer.serialize_bytes(&self.packed.to_be_bytes())
}
}
}
struct RawObjectIdVisitor;
impl<'de> Visitor<'de> for RawObjectIdVisitor {
type Value = RawObjectId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string or bytes representing an object id")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
RawObjectId::from_str(v).map_err(|e| E::custom(format!("Could not parse object id: {e}")))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
v.try_into()
.map(u128::from_be_bytes)
.map(RawObjectId::from_packed)
.map_err(|e| E::custom(format!("Could not parse object id: {e}")))
}
}
impl<'de> Deserialize<'de> for RawObjectId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(RawObjectIdVisitor)
} else {
deserializer.deserialize_bytes(RawObjectIdVisitor)
}
}
}
impl FromStr for RawObjectId {
type Err = RawObjectIdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let u128_id = u128::from_str_radix(s, 16)?;
let pad_length = s.len() as u128;
if u128_id > MAX_PACKED_VAL || pad_length > 24 {
return Err(RawObjectIdParseError::value_too_large(u128_id));
}
Ok(Self::from(u128_id << 32 | pad_length))
}
}
impl TryFrom<JsString> for RawObjectId {
type Error = RawObjectIdParseError;
fn try_from(js_id: JsString) -> Result<Self, Self::Error> {
let id: String = js_id.into();
RawObjectId::from_str(&id)
}
}
impl RawObjectId {
pub const fn from_packed(packed: u128) -> Self {
RawObjectId { packed }
}
pub fn to_array_string(&self) -> ArrayString<24> {
let mut res = ArrayString::new();
write!(res, "{self}").expect("expected formatting into a fixed-sized buffer to succeed");
res
}
}
impl From<RawObjectId> for ArrayString<24> {
fn from(id: RawObjectId) -> Self {
id.to_array_string()
}
}
impl From<RawObjectId> for String {
fn from(id: RawObjectId) -> Self {
id.to_string()
}
}
impl From<RawObjectId> for u128 {
fn from(id: RawObjectId) -> Self {
id.packed
}
}
impl From<u128> for RawObjectId {
fn from(packed: u128) -> Self {
Self::from_packed(packed)
}
}
#[cfg(test)]
mod test {
use super::RawObjectId;
const TEST_IDS: &[&str] = &[
"0",
"1",
"ffffffffffffffffffffffff",
"06aebab343040c9baaa22322",
"000000000000000000000001",
"100000000000000000000000",
"5bbcab1d9099fc012e632dbc",
"5bbcab1d9099fc012e632dbd",
"10000000000000000",
"1000000000000000",
"bc03381d32f6790",
"0df4aea318bd552",
"000000000000f00",
"100000000",
"10000000",
];
#[test]
fn rust_display_rust_fromstr_roundtrip() {
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
assert_eq!(&*parsed.to_string(), *id);
}
}
#[test]
fn rust_to_array_string_rust_fromstr_roundtrip() {
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
assert_eq!(&*parsed.to_array_string(), *id);
}
}
#[test]
fn rust_to_u128_from_u128_roundtrip() {
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
let int = u128::from(parsed);
let reparsed: RawObjectId = int.into();
assert_eq!(parsed, reparsed);
assert_eq!(reparsed.to_string(), *id);
}
}
#[test]
fn rust_to_serde_json_from_serde_json_roundtrip() {
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
let serialized = serde_json::to_string(&parsed).unwrap();
let reparsed: RawObjectId = serde_json::from_str(&serialized).unwrap();
assert_eq!(parsed, reparsed);
assert_eq!(reparsed.to_string(), *id);
}
}
#[test]
fn rust_vec_to_serde_json_from_serde_json_roundtrip() {
let mut ids_parsed = vec![];
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
ids_parsed.push(parsed);
}
let serialized = serde_json::to_string(&ids_parsed).unwrap();
let ids_reparsed: Vec<RawObjectId> = serde_json::from_str(&serialized).unwrap();
assert_eq!(ids_parsed, ids_reparsed);
}
#[test]
fn rust_to_serde_bincode_from_serde_bincode_roundtrip() {
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
let serialized = bincode::serialize(&parsed).unwrap();
let reparsed: RawObjectId = bincode::deserialize(&serialized).unwrap();
assert_eq!(parsed, reparsed);
assert_eq!(reparsed.to_string(), *id);
}
}
#[test]
fn rust_vec_to_serde_bincode_from_serde_bincode_roundtrip() {
let mut ids_parsed = vec![];
for id in TEST_IDS {
let parsed: RawObjectId = id.parse().unwrap();
ids_parsed.push(parsed);
}
let serialized = bincode::serialize(&ids_parsed).unwrap();
let ids_reparsed: Vec<RawObjectId> = bincode::deserialize(&serialized).unwrap();
assert_eq!(ids_parsed, ids_reparsed);
}
const INVALID_IDS: &[&str] = &[
"",
"-1",
"-0",
"1000000000000000000000000",
"000000000000000000000000f",
"340282366920938463463374607431768211455",
"000000000000000000000000000000000000000999000340282366920938463463374607431768211455",
"g",
"💣",
"\n",
];
#[test]
fn invalid_values_do_not_parse() {
for id in INVALID_IDS {
let res: Result<RawObjectId, _> = id.parse();
assert!(res.is_err());
}
}
}