ankurah_proto/
entity_id.rs

1use base64::{engine::general_purpose, Engine as _};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use ulid::Ulid;
5
6use wasm_bindgen::prelude::*;
7
8use crate::DecodeError;
9// TODO - split out the different id types. Presently there's a lot of not-entities that are using this type for their ID
10#[derive(PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd, Serialize, Deserialize)]
11#[wasm_bindgen]
12pub struct ID(Ulid);
13impl ID {
14    pub fn new() -> Self { ID(Ulid::new()) }
15
16    pub fn from_ulid(ulid: Ulid) -> Self { ID(ulid) }
17
18    pub fn to_bytes(&self) -> [u8; 16] { self.0.to_bytes() }
19
20    pub fn from_base64(base64_string: &str) -> Result<Self, DecodeError> {
21        let decoded = general_purpose::URL_SAFE_NO_PAD.decode(base64_string).map_err(|e| DecodeError::InvalidBase64(e))?;
22        let bytes: [u8; 16] = decoded[..].try_into().map_err(|_| DecodeError::InvalidLength)?;
23
24        Ok(ID(Ulid::from_bytes(bytes)))
25    }
26
27    pub fn to_base64(&self) -> String { general_purpose::URL_SAFE_NO_PAD.encode(self.0.to_bytes()) }
28}
29
30#[wasm_bindgen]
31impl ID {
32    pub fn as_string(&self) -> String { self.to_base64() }
33}
34
35impl fmt::Display for ID {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.to_base64()) }
37}
38impl Into<String> for ID {
39    fn into(self) -> String { self.to_base64() }
40}
41impl TryFrom<&str> for ID {
42    type Error = DecodeError;
43    fn try_from(id: &str) -> Result<Self, Self::Error> {
44        match Self::from_base64(id) {
45            Ok(id) => Ok(id),
46            Err(DecodeError::InvalidLength) => {
47                // fall back to ulid (base32) for compatibility with old ids
48                // REMOVE THIS ONCE ALL OLD IDS ARE CONVERTED TO BASE64
49                let ulid = Ulid::from_string(id).map_err(|_| DecodeError::InvalidUlid).map_err(|_| DecodeError::InvalidFallback)?;
50                Ok(ID::from_ulid(ulid))
51            }
52            Err(e) => Err(e),
53        }
54    }
55}
56
57impl TryFrom<String> for ID {
58    type Error = DecodeError;
59    fn try_from(id: String) -> Result<Self, Self::Error> { Self::try_from(id.as_str()) }
60}
61
62impl TryFrom<&String> for ID {
63    type Error = DecodeError;
64    fn try_from(id: &String) -> Result<Self, Self::Error> { Self::try_from(id.as_str()) }
65}
66
67impl TryFrom<JsValue> for ID {
68    type Error = DecodeError;
69    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
70        let id: String = value.as_string().ok_or(DecodeError::NotStringValue)?;
71        id.try_into()
72    }
73}
74
75impl std::fmt::Debug for ID {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0.to_string()) }
77}
78
79impl From<ID> for Ulid {
80    fn from(id: ID) -> Self { id.0 }
81}
82
83impl Default for ID {
84    fn default() -> Self { Self::new() }
85}