Atproto, add bound string

This commit is contained in:
Julia Lange 2025-07-02 10:48:44 -07:00
parent 5bc903b2fa
commit ee99f119f0
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto
4 changed files with 62 additions and 0 deletions

View file

@ -13,6 +13,7 @@ time = { version = "0.3.41", features = ["parsing", "formatting"] }
tracing-subscriber.workspace = true
tracing.workspace = true
thiserror.workspace = true
unicode-segmentation = "1.9.0"
[features]
default = []

View file

@ -24,6 +24,8 @@ pub enum ParseError {
Serde(#[from] serde_json::error::Error),
#[error("Length of parsed object too long, max: {max:?}, got: {got:?}.")]
Length { max: usize, got: usize },
#[error("Length of parsed object too short, min: {min:?}, got: {got:?}.")]
MinLength { min: usize, got: usize },
#[error("Currently Did is enforced, cannot use handle, {handle:?}")]
ForceDid { handle: String },
#[error("Incorrectly formatted")]

View file

@ -56,6 +56,7 @@ macro_rules! basic_string_type {
}
mod authority;
mod bound_string;
mod cid;
mod datetime;
mod did;
@ -63,6 +64,7 @@ mod record_key;
mod strong_ref;
mod uri;
pub use authority::Authority;
pub use bound_string::BoundString;
pub use cid::Cid;
pub use datetime::Datetime;
pub use did::Did;

View file

@ -0,0 +1,57 @@
use unicode_segmentation::UnicodeSegmentation;
use crate::error::{Error, ParseError};
use std::{
fmt::{Display, Formatter, Result as FmtResult},
str::FromStr,
};
pub struct BoundString<
const MIN: usize, const MAX: usize>
{
value: String,
}
impl<const MIN: usize, const MAX: usize> BoundString<MIN, MAX> {
fn check_length(s: &str) -> Result<(), Error> {
let grapheme_count: usize = s.graphemes(true).take(MAX + 1).count();
if grapheme_count > MAX {
return Err(Error::Parse {
err: ParseError::Length { max: MAX, got: grapheme_count },
object: "String".to_string(),
});
}
if grapheme_count < MIN {
return Err(Error::Parse {
err: ParseError::MinLength { min: MIN, got: grapheme_count },
object: "String".to_string(),
});
}
Ok(())
}
}
impl<const MIN: usize, const MAX: usize> Display for BoundString<MIN, MAX> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.value)
}
}
impl<const MIN: usize, const MAX: usize> FromStr for BoundString<MIN, MAX> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::check_length(s)?;
Ok(BoundString { value: s.to_string() })
}
}
impl<'de, const MIN: usize, const MAX: usize> serde::de::Deserialize<'de>
for BoundString<MIN, MAX> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let value: String = serde::de::Deserialize::deserialize(deserializer)?;
value.parse::<BoundString<MIN,MAX>>().map_err(<D::Error as serde::de::Error>::custom)
}
}