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