1use base64::{engine::general_purpose, Engine as _};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use ulid::Ulid;
5
6#[cfg(feature = "wasm")]
7use wasm_bindgen::prelude::*;
8
9use crate::error::DecodeError;
10#[derive(PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
12#[cfg_attr(feature = "wasm", wasm_bindgen)]
13pub struct EntityId(pub(crate) Ulid);
14
15impl EntityId {
16 pub fn new() -> Self { EntityId(Ulid::new()) }
17
18 pub fn from_bytes(bytes: [u8; 16]) -> Self { EntityId(Ulid::from_bytes(bytes)) }
19
20 pub fn to_bytes(&self) -> [u8; 16] { self.0.to_bytes() }
21
22 pub fn from_base64<T: AsRef<[u8]>>(input: T) -> Result<Self, DecodeError> {
23 let decoded = general_purpose::URL_SAFE_NO_PAD.decode(input).map_err(DecodeError::InvalidBase64)?;
24 let bytes: [u8; 16] = decoded[..].try_into().map_err(|_| DecodeError::InvalidLength)?;
25
26 Ok(EntityId(Ulid::from_bytes(bytes)))
27 }
28
29 pub fn to_base64(&self) -> String { general_purpose::URL_SAFE_NO_PAD.encode(self.0.to_bytes()) }
30
31 pub fn to_base64_short(&self) -> String {
32 let value = self.to_base64();
34 value[value.len() - 6..].to_string()
35 }
36
37 pub fn to_ulid(&self) -> Ulid { self.0 }
38 pub fn from_ulid(ulid: Ulid) -> Self { EntityId(ulid) }
39}
40
41#[cfg_attr(feature = "wasm", wasm_bindgen)]
42impl EntityId {
43 #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = toString))]
44 pub fn to_string(&self) -> String { self.to_base64() }
45
46 #[cfg(feature = "wasm")]
47 #[wasm_bindgen(js_name = to_base64)]
48 pub fn to_base64_js(&self) -> String { general_purpose::URL_SAFE_NO_PAD.encode(self.0.to_bytes()) }
49
50 #[cfg(feature = "wasm")]
51 #[wasm_bindgen(js_name = from_base64)]
52 pub fn from_base64_js(s: &str) -> Result<Self, JsValue> { Self::from_base64(s).map_err(|e| JsValue::from_str(&e.to_string())) }
53
54 #[cfg(feature = "wasm")]
55 #[wasm_bindgen]
56 pub fn equals(&self, other: &EntityId) -> bool { self.0 == other.0 }
57}
58
59impl fmt::Display for EntityId {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
61 if f.alternate() {
62 write!(f, "{}", self.to_base64_short())
63 } else {
64 write!(f, "{}", self.to_base64())
65 }
66 }
67}
68impl std::fmt::Debug for EntityId {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.to_base64()) }
70}
71
72impl TryFrom<&str> for EntityId {
73 type Error = DecodeError;
74 fn try_from(id: &str) -> Result<Self, Self::Error> { Self::from_base64(id) }
75}
76
77impl TryFrom<String> for EntityId {
78 type Error = DecodeError;
79 fn try_from(id: String) -> Result<Self, Self::Error> { Self::try_from(id.as_str()) }
80}
81
82impl TryFrom<&String> for EntityId {
83 type Error = DecodeError;
84 fn try_from(id: &String) -> Result<Self, Self::Error> { Self::try_from(id.as_str()) }
85}
86
87impl std::str::FromStr for EntityId {
88 type Err = DecodeError;
89 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_base64(s) }
90}
91
92impl From<EntityId> for String {
93 fn from(id: EntityId) -> String { id.to_base64() }
94}
95
96impl From<&EntityId> for String {
97 fn from(id: &EntityId) -> String { id.to_base64() }
98}
99
100impl TryInto<EntityId> for Vec<u8> {
101 type Error = DecodeError;
102 fn try_into(self) -> Result<EntityId, Self::Error> {
103 let bytes: [u8; 16] = self.try_into().map_err(|_| DecodeError::InvalidLength)?;
104 Ok(EntityId(Ulid::from_bytes(bytes)))
105 }
106}
107
108impl From<EntityId> for Ulid {
109 fn from(id: EntityId) -> Self { id.0 }
110}
111
112impl Default for EntityId {
113 fn default() -> Self { Self::new() }
114}
115
116impl Serialize for EntityId {
117 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118 where S: serde::Serializer {
119 if serializer.is_human_readable() {
120 serializer.serialize_str(&self.to_base64())
122 } else {
123 self.to_bytes().serialize(serializer)
125 }
126 }
127}
128
129impl<'de> Deserialize<'de> for EntityId {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where D: serde::Deserializer<'de> {
132 if deserializer.is_human_readable() {
133 let s = String::deserialize(deserializer)?;
135 EntityId::from_base64(s).map_err(serde::de::Error::custom)
136 } else {
137 let bytes = <[u8; 16]>::deserialize(deserializer)?;
139 Ok(EntityId::from_bytes(bytes))
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_entity_id_json_serialization() {
150 let id = EntityId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
151 let json = serde_json::to_string(&id).unwrap();
152 assert_eq!(json, "\"AQIDBAUGBwgJCgsMDQ4PEA\"");
153 assert_eq!(id, serde_json::from_str(&json).unwrap());
154 }
155
156 #[test]
157 fn test_entity_id_bincode_serialization() {
158 let id = EntityId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
159 let bytes = bincode::serialize(&id).unwrap();
160 assert_eq!(bytes, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
161 assert_eq!(id, bincode::deserialize(&bytes).unwrap());
162 }
163}
164
165impl From<EntityId> for ankql::ast::Expr {
168 fn from(id: EntityId) -> ankql::ast::Expr { ankql::ast::Expr::Literal(ankql::ast::Literal::EntityId(id.to_ulid())) }
169}
170
171impl From<&EntityId> for ankql::ast::Expr {
172 fn from(id: &EntityId) -> ankql::ast::Expr { ankql::ast::Expr::Literal(ankql::ast::Literal::EntityId(id.to_ulid())) }
173}