use std::fmt; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; pub const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct NullableDto(pub Option); pub(crate) fn optional_nullable<'de, D, T>( deserializer: D, ) -> Result>, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { NullableDto::deserialize(deserializer).map(Some) } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct SafeIntegerDto(u64); impl SafeIntegerDto { pub fn new(value: u64) -> Result { if value > MAX_SAFE_INTEGER { return Err(WireValueError::SafeInteger); } Ok(Self(value)) } #[must_use] pub const fn get(self) -> u64 { self.0 } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct SignedSafeIntegerDto(i64); impl SignedSafeIntegerDto { pub fn new(value: i64) -> Result { if value.unsigned_abs() > MAX_SAFE_INTEGER { return Err(WireValueError::SafeInteger); } Ok(Self(value)) } #[must_use] pub const fn get(self) -> i64 { self.0 } } impl Serialize for SignedSafeIntegerDto { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_i64(self.0) } } impl<'de> Deserialize<'de> for SignedSafeIntegerDto { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Self::new(i64::deserialize(deserializer)?).map_err(D::Error::custom) } } impl Serialize for SafeIntegerDto { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_u64(self.0) } } impl<'de> Deserialize<'de> for SafeIntegerDto { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Self::new(u64::deserialize(deserializer)?).map_err(D::Error::custom) } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct PositiveIntegerDto(SafeIntegerDto); impl PositiveIntegerDto { pub fn new(value: u64) -> Result { let value = SafeIntegerDto::new(value)?; if value.get() == 0 { return Err(WireValueError::PositiveInteger); } Ok(Self(value)) } #[must_use] pub const fn get(self) -> u64 { self.0.get() } } impl Serialize for PositiveIntegerDto { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_u64(self.get()) } } impl<'de> Deserialize<'de> for PositiveIntegerDto { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Self::new(u64::deserialize(deserializer)?).map_err(D::Error::custom) } } #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub struct SchemaVersionDto; impl Serialize for SchemaVersionDto { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_u8(1) } } impl<'de> Deserialize<'de> for SchemaVersionDto { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { if u8::deserialize(deserializer)? != 1 { return Err(D::Error::custom("unsupported schema version")); } Ok(Self) } } #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct CursorDto(String); impl CursorDto { pub fn parse(value: impl Into) -> Result { let value = value.into(); if !(16..=2048).contains(&value.chars().count()) { return Err(WireValueError::Cursor); } Ok(Self(value)) } #[must_use] pub fn as_str(&self) -> &str { &self.0 } } impl Serialize for CursorDto { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.0) } } impl<'de> Deserialize<'de> for CursorDto { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Self::parse(String::deserialize(deserializer)?).map_err(D::Error::custom) } } #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DateTimeDto(String); impl DateTimeDto { pub fn parse(value: impl Into) -> Result { let value = value.into(); OffsetDateTime::parse(&value, &Rfc3339).map_err(|_| WireValueError::DateTime)?; Ok(Self(value)) } #[must_use] pub fn as_str(&self) -> &str { &self.0 } } impl Serialize for DateTimeDto { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.0) } } impl<'de> Deserialize<'de> for DateTimeDto { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Self::parse(String::deserialize(deserializer)?).map_err(D::Error::custom) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum WireValueError { SafeInteger, PositiveInteger, Cursor, DateTime, } impl fmt::Display for WireValueError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { let message = match self { Self::SafeInteger => "integer exceeds the interoperable JSON range", Self::PositiveInteger => "expected a positive integer", Self::Cursor => "invalid cursor length", Self::DateTime => "invalid RFC 3339 date-time", }; formatter.write_str(message) } } impl std::error::Error for WireValueError {}