1use std::str::FromStr;
2
3use thiserror::Error;
4use time::OffsetDateTime;
5use uuid::Uuid;
6
7#[derive(Debug, Clone)]
8pub struct Project<D> {
9 pub id: Id,
10 pub created_at: OffsetDateTime,
11 pub modified_at: Option<OffsetDateTime>,
12 pub data: D,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Hash)]
16pub struct Id(Uuid);
17
18impl Id {
19 #[must_use]
20 #[allow(clippy::new_without_default)]
21 pub fn new() -> Self {
22 Self(Uuid::new_v4())
23 }
24
25 #[must_use]
26 pub const fn from_uuid(uuid: Uuid) -> Self {
27 Self(uuid)
28 }
29
30 #[must_use]
31 pub const fn to_uuid(&self) -> Uuid {
32 self.0
33 }
34}
35
36impl ToString for Id {
37 fn to_string(&self) -> String {
38 self.0.simple().to_string()
39 }
40}
41
42#[derive(Debug, Error)]
43#[error("Invalid project ID")]
44pub struct IdParseError;
45
46impl FromStr for Id {
47 type Err = IdParseError;
48 fn from_str(from: &str) -> Result<Self, Self::Err> {
49 let uuid = from.parse::<Uuid>().map_err(|_| IdParseError)?;
50 Ok(Self(uuid))
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use uuid::uuid;
58
59 const EXAMPLE_ID: Uuid = uuid!("157296c3-4a0c-4794-91bb-34008da55535");
60
61 #[test]
62 fn id_to_string() {
63 let id = Id::from_uuid(EXAMPLE_ID);
64 assert_eq!(id.to_string(), "157296c34a0c479491bb34008da55535");
65 }
66
67 #[test]
68 fn parse() {
69 let id = "157296c3-4a0c-4794-91bb-34008da55535"
70 .parse::<Id>()
71 .unwrap();
72 assert_eq!(id.0, EXAMPLE_ID);
73
74 let id = "157296c34a0c479491bb34008da55535".parse::<Id>().unwrap();
75 assert_eq!(id.0, EXAMPLE_ID);
76
77 let id = "157296C34A0C479491BB34008DA55535".parse::<Id>().unwrap();
78 assert_eq!(id.0, EXAMPLE_ID);
79 }
80}