use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SuggestId {
index: u32,
generation: u32,
}
impl SuggestId {
pub fn new(index: u32, generation: u32) -> Self {
Self { index, generation }
}
#[allow(dead_code)]
pub(crate) fn from_raw(index: u32, generation: u32) -> Self {
Self { index, generation }
}
pub fn index(&self) -> u32 {
self.index
}
pub fn generation(&self) -> u32 {
self.generation
}
pub fn is_generation(&self, gen: u32) -> bool {
self.generation == gen
}
pub fn with_generation(&self, generation: u32) -> Self {
Self {
index: self.index,
generation,
}
}
}
impl fmt::Display for SuggestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "S{:03}g{}", self.index, self.generation)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseSuggestIdError {
InvalidFormat,
InvalidIndex,
InvalidGeneration,
}
impl fmt::Display for ParseSuggestIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat => write!(f, "Invalid SuggestId format (expected S###g#)"),
Self::InvalidIndex => write!(f, "Invalid index in SuggestId"),
Self::InvalidGeneration => write!(f, "Invalid generation in SuggestId"),
}
}
}
impl std::error::Error for ParseSuggestIdError {}
impl FromStr for SuggestId {
type Err = ParseSuggestIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let rest = s
.strip_prefix('S')
.or_else(|| s.strip_prefix('s'))
.ok_or(ParseSuggestIdError::InvalidFormat)?;
let (index_str, gen_str) = rest
.split_once('g')
.or_else(|| rest.split_once('G'))
.ok_or(ParseSuggestIdError::InvalidFormat)?;
let index: u32 = index_str
.parse()
.map_err(|_| ParseSuggestIdError::InvalidIndex)?;
let generation: u32 = gen_str
.parse()
.map_err(|_| ParseSuggestIdError::InvalidGeneration)?;
Ok(SuggestId { index, generation })
}
}
impl Serialize for SuggestId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for SuggestId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone)]
pub struct SuggestIdGenerator {
next_index: u32,
}
impl Default for SuggestIdGenerator {
fn default() -> Self {
Self::new()
}
}
impl SuggestIdGenerator {
pub fn new() -> Self {
Self { next_index: 1 }
}
pub fn starting_at(index: u32) -> Self {
Self {
next_index: index.max(1),
}
}
pub fn next_id(&mut self) -> SuggestId {
let id = SuggestId::new(self.next_index, 0);
self.next_index += 1;
id
}
pub fn next_id_with_generation(&mut self, generation: u32) -> SuggestId {
let id = SuggestId::new(self.next_index, generation);
self.next_index += 1;
id
}
pub fn peek(&self) -> SuggestId {
SuggestId::new(self.next_index, 0)
}
pub fn reset(&mut self) {
self.next_index = 1;
}
pub fn current_index(&self) -> u32 {
self.next_index
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_suggest_id_new() {
let id = SuggestId::new(1, 0);
assert_eq!(id.index(), 1);
assert_eq!(id.generation(), 0);
}
#[test]
fn test_suggest_id_display() {
assert_eq!(SuggestId::new(1, 0).to_string(), "S001g0");
assert_eq!(SuggestId::new(42, 3).to_string(), "S042g3");
assert_eq!(SuggestId::new(999, 10).to_string(), "S999g10");
assert_eq!(SuggestId::new(1000, 0).to_string(), "S1000g0");
}
#[test]
fn test_suggest_id_parse() {
let id: SuggestId = "S001g0".parse().unwrap();
assert_eq!(id.index(), 1);
assert_eq!(id.generation(), 0);
let id2: SuggestId = "S042g3".parse().unwrap();
assert_eq!(id2.index(), 42);
assert_eq!(id2.generation(), 3);
let id3: SuggestId = "s123G5".parse().unwrap();
assert_eq!(id3.index(), 123);
assert_eq!(id3.generation(), 5);
}
#[test]
fn test_suggest_id_parse_errors() {
assert_eq!(
"001g0".parse::<SuggestId>().unwrap_err(),
ParseSuggestIdError::InvalidFormat
);
assert_eq!(
"S001".parse::<SuggestId>().unwrap_err(),
ParseSuggestIdError::InvalidFormat
);
assert_eq!(
"Sabcg0".parse::<SuggestId>().unwrap_err(),
ParseSuggestIdError::InvalidIndex
);
assert_eq!(
"S001gabc".parse::<SuggestId>().unwrap_err(),
ParseSuggestIdError::InvalidGeneration
);
}
#[test]
fn test_suggest_id_serde() {
let id = SuggestId::new(42, 3);
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "\"S042g3\"");
let parsed: SuggestId = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, id);
}
#[test]
fn test_suggest_id_ordering() {
let id1 = SuggestId::new(1, 0);
let id2 = SuggestId::new(2, 0);
let id3 = SuggestId::new(1, 1);
assert!(id1 < id2);
assert!(id1 < id3);
let mut ids = vec![id2, id3, id1];
ids.sort();
assert_eq!(ids, vec![id1, id3, id2]);
}
#[test]
fn test_suggest_id_is_generation() {
let id = SuggestId::new(1, 3);
assert!(id.is_generation(3));
assert!(!id.is_generation(0));
assert!(!id.is_generation(4));
}
#[test]
fn test_suggest_id_with_generation() {
let id = SuggestId::new(42, 0);
let bumped = id.with_generation(5);
assert_eq!(bumped.index(), 42);
assert_eq!(bumped.generation(), 5);
}
#[test]
fn test_suggest_id_generator() {
let mut gen = SuggestIdGenerator::new();
assert_eq!(gen.next_id(), SuggestId::new(1, 0));
assert_eq!(gen.next_id(), SuggestId::new(2, 0));
assert_eq!(gen.next_id(), SuggestId::new(3, 0));
assert_eq!(gen.peek(), SuggestId::new(4, 0));
assert_eq!(gen.next_id(), SuggestId::new(4, 0));
}
#[test]
fn test_suggest_id_generator_with_generation() {
let mut gen = SuggestIdGenerator::new();
let id = gen.next_id_with_generation(5);
assert_eq!(id.index(), 1);
assert_eq!(id.generation(), 5);
}
#[test]
fn test_suggest_id_generator_starting_at() {
let mut gen = SuggestIdGenerator::starting_at(10);
assert_eq!(gen.next_id(), SuggestId::new(10, 0));
assert_eq!(gen.next_id(), SuggestId::new(11, 0));
}
#[test]
fn test_suggest_id_generator_reset() {
let mut gen = SuggestIdGenerator::new();
gen.next_id();
gen.next_id();
gen.reset();
assert_eq!(gen.next_id(), SuggestId::new(1, 0));
}
}