Skip to main content

horfimbor_eventsource/
model_key.rs

1//! `ModelKey` is the entity unique id
2
3use crate::StreamName;
4use serde::{Deserialize, Serialize};
5use std::fmt::{Display, Formatter};
6use uuid::{Error as UuidError, Uuid};
7
8use sha1::{Digest, Sha1};
9use thiserror::Error;
10
11/// container for the entity key
12#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq, Default, Hash)]
13pub struct ModelKey {
14    stream_name: String,
15    stream_id: Uuid,
16}
17
18/// the model key allow to create in a safe way the identifier of the entity
19impl ModelKey {
20    /// the model key is created with a stream name representing the domain
21    /// and an uuid, the id of the entity
22    #[must_use]
23    pub fn new(stream_name: StreamName, stream_id: Uuid) -> Self {
24        // maybe replace with an error ?
25        let name = stream_name.replace('-', "_");
26        Self {
27            stream_name: name,
28            stream_id,
29        }
30    }
31
32    /// the model key is created with an uuid v4, to use only to create a new entity
33    #[must_use]
34    pub fn new_uuid_v4(stream_name: StreamName) -> Self {
35        // maybe replace with an error ?
36        let name = stream_name.replace('-', "_");
37        let stream_id = Uuid::new_v4();
38        Self {
39            stream_name: name,
40            stream_id,
41        }
42    }
43
44    /// the model key is created with an uuid v7, to use only to create a new entity
45    #[must_use]
46    pub fn new_uuid_v7(stream_name: StreamName) -> Self {
47        // maybe replace with an error ?
48        let name = stream_name.replace('-', "_");
49        let stream_id = Uuid::now_v7();
50        Self {
51            stream_name: name,
52            stream_id,
53        }
54    }
55
56    /// the model key is created for a UUID created from external data
57    #[must_use]
58    pub fn new_uuid_v8(stream_name: StreamName, kind: &'static str, data: &str) -> Self {
59        let mut hasher = Sha1::new();
60        hasher.update(kind);
61        hasher.update(data);
62        let hash = hasher.finalize();
63        let result = hash.as_slice();
64
65        let mut bytes = [0; 16];
66        bytes.copy_from_slice(&result[..16]);
67
68        let stream_id = Uuid::new_v8(bytes);
69        Self::new(stream_name, stream_id)
70    }
71
72    /// the main purpose of the `ModelKey` is to provide this string.
73    #[must_use]
74    pub fn format(&self) -> String {
75        format!("{}-{}", self.stream_name.replace('.', "_"), self.stream_id)
76    }
77}
78
79/// the multiple in which the try from can fail
80#[derive(Error, Debug)]
81pub enum ModelKeyError {
82    /// error bubble up from Uuid
83    #[error("uuid error`{0}`")]
84    UuidError(#[from] UuidError),
85
86    /// the string wasnt
87    #[error("the parameter was empty")]
88    Empty,
89}
90
91impl TryFrom<&str> for ModelKey {
92    type Error = ModelKeyError;
93
94    fn try_from(value: &str) -> Result<Self, Self::Error> {
95        let mut split = value.split('-');
96        let Some(stream_name) = split.next() else {
97            return Err(ModelKeyError::Empty);
98        };
99        let to_check_uuid = split.collect::<Vec<&str>>().join("-");
100        let stream_id = Uuid::parse_str(&to_check_uuid)?;
101        Ok(Self {
102            stream_name: stream_name.to_string(),
103            stream_id,
104        })
105    }
106}
107
108impl Display for ModelKey {
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        write!(f, "{}-{}", self.stream_name, self.stream_id)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    // Note this useful idiom: importing names from outer (for mod tests) scope.
117    use super::*;
118
119    #[test]
120    fn test_format() {
121        let m = ModelKey::new(
122            "mpzNpYJ",
123            Uuid::parse_str("01797a2e-19de-467c-bda2-eddc2a2cbf8c").unwrap(),
124        );
125
126        assert_eq!(
127            m.format(),
128            "mpzNpYJ-01797a2e-19de-467c-bda2-eddc2a2cbf8c".to_string()
129        );
130    }
131
132    #[test]
133    fn test_from() {
134        let m = ModelKey::new(
135            "mpzNpYJ",
136            Uuid::parse_str("01797a2e-19de-467c-bda2-eddc2a2cbf8c").unwrap(),
137        );
138
139        let f: ModelKey = "mpzNpYJ-01797a2e-19de-467c-bda2-eddc2a2cbf8c"
140            .try_into()
141            .unwrap();
142
143        assert_eq!(f, m);
144    }
145}