pub const MAX_LENGTH: usize = 30;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdempotencyKey(String);
impl IdempotencyKey {
pub fn new(key: impl Into<String>) -> Result<Self, IdempotencyKeyError> {
let key = key.into();
if key.is_empty() {
return Err(IdempotencyKeyError::Empty);
}
if key.len() > MAX_LENGTH {
return Err(IdempotencyKeyError::TooLong {
length: key.len(),
max: MAX_LENGTH,
});
}
Ok(Self(key))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl std::fmt::Display for IdempotencyKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for IdempotencyKey {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::str::FromStr for IdempotencyKey {
type Err = IdempotencyKeyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum IdempotencyKeyError {
#[error("idempotency key cannot be empty")]
Empty,
#[error("idempotency key is {length} bytes; the Snippe API limit is {max}")]
TooLong {
length: usize,
max: usize,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_short_key() {
let k = IdempotencyKey::new("ord-1").unwrap();
assert_eq!(k.as_str(), "ord-1");
}
#[test]
fn accepts_exactly_30_bytes() {
let k = IdempotencyKey::new("a".repeat(30)).unwrap();
assert_eq!(k.as_str().len(), 30);
}
#[test]
fn rejects_31_bytes() {
let err = IdempotencyKey::new("a".repeat(31)).unwrap_err();
assert_eq!(err, IdempotencyKeyError::TooLong { length: 31, max: 30 });
}
#[test]
fn rejects_empty() {
assert_eq!(IdempotencyKey::new("").unwrap_err(), IdempotencyKeyError::Empty);
}
#[test]
fn from_str_works() {
let k: IdempotencyKey = "ord-1".parse().unwrap();
assert_eq!(k.as_str(), "ord-1");
}
}