1use std::fmt::Display;
7
8#[repr(u16)]
9#[non_exhaustive]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum NodeKind {
12 User = 0,
13 SocialAuth = 1,
14 Namespace = 2,
15 Package = 3,
16 PackageVersion = 4,
17 PackageCollaborator = 5,
18 PackageCollaboratorInvite = 6,
19 NativeExecutable = 7,
20 PackageVersionNPMBinding = 8,
21 PackageVersionPythonBinding = 9,
22 PackageTransferRequest = 10,
23 Interface = 11,
24 InterfaceVersion = 12,
25 PublicKey = 13,
26 UserNotification = 14,
27 ActivityEvent = 15,
28 NamespaceCollaborator = 16,
29 NamespaceCollaboratorInvite = 17,
30 BindingsGenerator = 18,
31 DeployConfigVersion = 19,
32 DeployConfigInfo = 20,
33 DeployApp = 21,
34 DeployAppVersion = 22,
35 Waitlist = 23,
36 WaitlistMember = 24,
37 CardPaymentMethod = 25,
38 PaymentIntent = 26,
39 AppAlias = 27,
40 Nonce = 28,
41 TermsOfService = 29,
42}
43
44impl NodeKind {
45 pub fn from_num(x: u64) -> Option<Self> {
46 match x {
47 0 => Some(Self::User),
48 1 => Some(Self::SocialAuth),
49 2 => Some(Self::Namespace),
50 3 => Some(Self::Package),
51 4 => Some(Self::PackageVersion),
52 5 => Some(Self::PackageCollaborator),
53 6 => Some(Self::PackageCollaboratorInvite),
54 7 => Some(Self::NativeExecutable),
55 8 => Some(Self::PackageVersionNPMBinding),
56 9 => Some(Self::PackageVersionPythonBinding),
57 10 => Some(Self::PackageTransferRequest),
58 11 => Some(Self::Interface),
59 12 => Some(Self::InterfaceVersion),
60 13 => Some(Self::PublicKey),
61 14 => Some(Self::UserNotification),
62 15 => Some(Self::ActivityEvent),
63 16 => Some(Self::NamespaceCollaborator),
64 17 => Some(Self::NamespaceCollaboratorInvite),
65 18 => Some(Self::BindingsGenerator),
66 19 => Some(Self::DeployConfigVersion),
67 20 => Some(Self::DeployConfigInfo),
68 21 => Some(Self::DeployApp),
69 22 => Some(Self::DeployAppVersion),
70 23 => Some(Self::Waitlist),
71 24 => Some(Self::WaitlistMember),
72 25 => Some(Self::CardPaymentMethod),
73 26 => Some(Self::PaymentIntent),
74 27 => Some(Self::AppAlias),
75 28 => Some(Self::Nonce),
76 29 => Some(Self::TermsOfService),
77 _ => None,
78 }
79 }
80
81 pub fn parse_prefix(s: &str) -> Option<NodeKind> {
82 match s {
83 "u" => Some(Self::User),
84 "su" => Some(Self::SocialAuth),
85 "ns" => Some(Self::Namespace),
86 "pk" => Some(Self::Package),
87 "pkv" => Some(Self::PackageVersion),
88 "pc" => Some(Self::PackageCollaborator),
89 "pci" => Some(Self::PackageCollaboratorInvite),
90 "ne" => Some(Self::NativeExecutable),
91 "pkvbjs" => Some(Self::PackageVersionNPMBinding),
92 "pkvbpy" => Some(Self::PackageVersionPythonBinding),
93 "pt" => Some(Self::PackageTransferRequest),
94 "in" => Some(Self::Interface),
95 "inv" => Some(Self::InterfaceVersion),
96 "pub" => Some(Self::PublicKey),
97 "nt" => Some(Self::UserNotification),
98 "ae" => Some(Self::ActivityEvent),
99 "nsc" => Some(Self::NamespaceCollaborator),
100 "nsci" => Some(Self::NamespaceCollaboratorInvite),
101 "bg" => Some(Self::BindingsGenerator),
102 "dcv" => Some(Self::DeployConfigVersion),
103 "dci" => Some(Self::DeployConfigInfo),
104 "da" => Some(Self::DeployApp),
105 "dav" => Some(Self::DeployAppVersion),
106 "wl" => Some(Self::Waitlist),
107 "wlm" => Some(Self::WaitlistMember),
108 "cpm" => Some(Self::CardPaymentMethod),
109 "pi" => Some(Self::PaymentIntent),
110 "daa" => Some(Self::AppAlias),
111 "nnc" => Some(Self::Nonce),
112 "tos" => Some(Self::TermsOfService),
113 _ => None,
114 }
115 }
116
117 fn as_prefix(&self) -> &'static str {
118 match self {
119 Self::User => "u",
120 Self::SocialAuth => "su",
121 Self::Namespace => "ns",
122 Self::Package => "pk",
123 Self::PackageVersion => "pkv",
124 Self::PackageCollaborator => "pc",
125 Self::PackageCollaboratorInvite => "pci",
126 Self::NativeExecutable => "ne",
127 Self::PackageVersionNPMBinding => "pkvbjs",
128 Self::PackageVersionPythonBinding => "pkvbpy",
129 Self::PackageTransferRequest => "pt",
130 Self::Interface => "in",
131 Self::InterfaceVersion => "inv",
132 Self::PublicKey => "pub",
133 Self::UserNotification => "nt",
134 Self::ActivityEvent => "ae",
135 Self::NamespaceCollaborator => "nsc",
136 Self::NamespaceCollaboratorInvite => "nsci",
137 Self::BindingsGenerator => "bg",
138 Self::DeployConfigVersion => "dcv",
139 Self::DeployConfigInfo => "dci",
140 Self::DeployApp => "da",
141 Self::DeployAppVersion => "dav",
142 Self::Waitlist => "wl",
143 Self::WaitlistMember => "wlm",
144 Self::CardPaymentMethod => "cpm",
145 Self::PaymentIntent => "pi",
146 Self::AppAlias => "daa",
147 Self::Nonce => "nnc",
148 Self::TermsOfService => "tos",
149 }
150 }
151}
152
153impl Display for NodeKind {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 let name = match self {
156 Self::User => "User",
157 Self::SocialAuth => "SocialAuth",
158 Self::Namespace => "Namespace",
159 Self::Package => "Package",
160 Self::PackageVersion => "PackageVersion",
161 Self::PackageCollaborator => "PackageCollaborator",
162 Self::PackageCollaboratorInvite => "PackageCollaboratorInvite",
163 Self::NativeExecutable => "NativeExecutable",
164 Self::PackageVersionNPMBinding => "PackageVersionNPMBinding",
165 Self::PackageVersionPythonBinding => "PackageVersionPythonBinding",
166 Self::PackageTransferRequest => "PackageTransferRequest",
167 Self::Interface => "Interface",
168 Self::InterfaceVersion => "InterfaceVersion",
169 Self::PublicKey => "PublicKey",
170 Self::UserNotification => "UserNotification",
171 Self::ActivityEvent => "ActivityEvent",
172 Self::NamespaceCollaborator => "NamespaceCollaborator",
173 Self::NamespaceCollaboratorInvite => "NamespaceCollaboratorInvite",
174 Self::BindingsGenerator => "BindingsGenerator",
175 Self::DeployConfigVersion => "DeployConfigVersion",
176 Self::DeployConfigInfo => "DeployConfigInfo",
177 Self::DeployApp => "DeployApp",
178 Self::DeployAppVersion => "DeployAppVersion",
179 Self::Waitlist => "Waitlist",
180 Self::WaitlistMember => "WaitlistMember",
181 Self::CardPaymentMethod => "CardPaymentMethod",
182 Self::PaymentIntent => "PaymentIntent",
183 Self::AppAlias => "AppAlias",
184 Self::Nonce => "Nonce",
185 Self::TermsOfService => "TermsOfService",
186 };
187 write!(f, "{name}")
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196pub struct GlobalId {
197 kind: NodeKind,
199 database_id: u64,
201}
202
203impl GlobalId {
204 const SALT: &'static str = "wasmer salt hashid";
206 const MIN_LENGTH: usize = 12;
208
209 const ALPHABET_PREFIXED: &'static str =
211 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
212
213 const ALPHABET_URL: &'static str = "abcdefghijklmnopqrstuvwxyz0123456789";
215
216 pub fn new(kind: NodeKind, database_id: u64) -> Self {
217 Self { kind, database_id }
218 }
219
220 fn build_harsh(alphabet: &str, salt: &[u8]) -> harsh::Harsh {
221 harsh::HarshBuilder::new()
222 .alphabet(alphabet.as_bytes())
223 .salt(salt)
224 .length(GlobalId::MIN_LENGTH)
225 .build()
226 .unwrap()
227 }
228
229 fn build_harsh_prefixed() -> harsh::Harsh {
230 Self::build_harsh(Self::ALPHABET_PREFIXED, Self::SALT.as_bytes())
231 }
232
233 fn build_harsh_url() -> harsh::Harsh {
234 Self::build_harsh(Self::ALPHABET_URL, Self::SALT.as_bytes())
235 }
236
237 pub fn kind(&self) -> NodeKind {
238 self.kind
239 }
240
241 pub fn database_id(&self) -> u64 {
242 self.database_id
243 }
244
245 pub fn encode_prefixed(&self) -> String {
247 let hash = Self::build_harsh_prefixed().encode(&[
248 1,
250 2,
252 self.kind as u64,
253 self.database_id,
254 ]);
255
256 format!("{}_{}", self.kind.as_prefix(), hash)
257 }
258
259 fn parse_values(values: &[u64]) -> Result<Self, ErrorKind> {
260 let scope = values.first().cloned().ok_or(ErrorKind::MissingScope)?;
261
262 if scope != 1 {
263 return Err(ErrorKind::UnknownScope(scope));
264 }
265
266 let version = values.get(1).cloned().ok_or(ErrorKind::MissingVersion)?;
267 if version != 2 {
268 return Err(ErrorKind::UnknownVersion(version));
269 }
270
271 let ty_raw = values.get(2).cloned().ok_or(ErrorKind::MissingNodeType)?;
272 let ty_parsed = NodeKind::from_num(ty_raw).ok_or(ErrorKind::UnknownNodeType(ty_raw))?;
273
274 let db_id = values.get(3).cloned().ok_or(ErrorKind::MissingDatabaseId)?;
275
276 Ok(Self {
277 kind: ty_parsed,
278 database_id: db_id,
279 })
280 }
281
282 pub fn parse_prefixed(hash: &str) -> Result<Self, GlobalIdParseError> {
284 let (prefix, value) = hash
285 .split_once('_')
286 .ok_or_else(|| GlobalIdParseError::new(hash, ErrorKind::MissingPrefix))?;
287
288 if prefix.is_empty() {
289 return Err(GlobalIdParseError::new(hash, ErrorKind::MissingPrefix));
290 }
291
292 let ty_prefix = NodeKind::parse_prefix(prefix).ok_or_else(|| {
293 GlobalIdParseError::new(hash, ErrorKind::UnknownPrefix(prefix.to_string()))
294 })?;
295
296 let values = Self::build_harsh_prefixed()
297 .decode(value)
298 .map_err(|err| GlobalIdParseError::new(hash, ErrorKind::Decode(err.to_string())))?;
299
300 let s = Self::parse_values(&values).map_err(|kind| GlobalIdParseError::new(hash, kind))?;
301
302 if ty_prefix != s.kind {
303 return Err(GlobalIdParseError::new(hash, ErrorKind::PrefixTypeMismatch));
304 }
305
306 Ok(s)
307 }
308
309 pub fn encode_url(&self) -> String {
313 Self::build_harsh_url().encode(&[
314 1,
316 2,
318 self.kind as u64,
319 self.database_id,
320 ])
321 }
322
323 pub fn parse_url(hash: &str) -> Result<Self, GlobalIdParseError> {
327 let values = Self::build_harsh_url()
328 .decode(hash)
329 .map_err(|err| GlobalIdParseError::new(hash, ErrorKind::Decode(err.to_string())))?;
330
331 Self::parse_values(&values).map_err(|kind| GlobalIdParseError::new(hash, kind))
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
336pub struct GlobalIdParseError {
337 id: String,
338 kind: ErrorKind,
339}
340
341impl GlobalIdParseError {
342 fn new(id: impl Into<String>, kind: ErrorKind) -> Self {
343 Self {
344 id: id.into(),
345 kind,
346 }
347 }
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
353#[non_exhaustive]
354enum ErrorKind {
355 MissingPrefix,
356 UnknownPrefix(String),
357 PrefixTypeMismatch,
358 MissingScope,
359 UnknownScope(u64),
360 MissingVersion,
361 UnknownVersion(u64),
362 MissingNodeType,
363 UnknownNodeType(u64),
364 MissingDatabaseId,
365 Decode(String),
366}
367
368impl std::fmt::Display for GlobalIdParseError {
369 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
370 write!(f, "could not parse global id '{}': ", self.id)?;
371
372 match &self.kind {
373 ErrorKind::UnknownPrefix(p) => {
374 write!(f, "unknown type prefix '{p}'")
375 }
376 ErrorKind::Decode(s) => {
377 write!(f, "decode error: {s}")
378 }
379 ErrorKind::MissingScope => {
380 write!(f, "missing scope value")
381 }
382 ErrorKind::UnknownScope(x) => {
383 write!(f, "unknown scope value {x}")
384 }
385 ErrorKind::MissingVersion => {
386 write!(f, "missing version value")
387 }
388 ErrorKind::UnknownVersion(v) => {
389 write!(f, "unknown version value {v}")
390 }
391 ErrorKind::UnknownNodeType(t) => {
392 write!(f, "unknown node type '{t}'")
393 }
394 ErrorKind::MissingPrefix => write!(f, "missing prefix"),
395 ErrorKind::PrefixTypeMismatch => write!(f, "prefix type mismatch"),
396 ErrorKind::MissingNodeType => write!(f, "missing node type"),
397 ErrorKind::MissingDatabaseId => write!(f, "missing database id"),
398 }
399 }
400}
401
402impl std::error::Error for GlobalIdParseError {}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_global_id() {
410 let x1 = GlobalId {
412 kind: NodeKind::DeployApp,
413 database_id: 123,
414 };
415 assert_eq!(Ok(x1), GlobalId::parse_prefixed(&x1.encode_prefixed()));
416 assert_eq!(Ok(x1), GlobalId::parse_url(&x1.encode_url()));
417
418 assert_eq!(
419 GlobalId::parse_prefixed("da_MRrWI0t5U582"),
420 Ok(GlobalId {
421 kind: NodeKind::DeployApp,
422 database_id: 273,
423 })
424 );
425
426 assert_eq!(
428 GlobalId::parse_prefixed("oOtQIDI7q").err().unwrap().kind,
429 ErrorKind::MissingPrefix,
430 );
431 assert_eq!(
432 GlobalId::parse_prefixed("oOtQIDI7q").err().unwrap().kind,
433 ErrorKind::MissingPrefix,
434 );
435 assert_eq!(
436 GlobalId::parse_prefixed("_oOtQIDI7q").err().unwrap().kind,
437 ErrorKind::MissingPrefix,
438 );
439 assert_eq!(
440 GlobalId::parse_prefixed("lala_oOtQIDI7q")
441 .err()
442 .unwrap()
443 .kind,
444 ErrorKind::UnknownPrefix("lala".to_string()),
445 );
446
447 let kind = GlobalId::parse_prefixed("da_xxx").err().unwrap().kind;
448 assert!(matches!(kind, ErrorKind::Decode(_)));
449 }
450
451 #[test]
452 fn test_global_id_parse_values() {
453 assert_eq!(GlobalId::parse_values(&[]), Err(ErrorKind::MissingScope));
454 assert_eq!(
455 GlobalId::parse_values(&[2]),
456 Err(ErrorKind::UnknownScope(2)),
457 );
458 assert_eq!(GlobalId::parse_values(&[1]), Err(ErrorKind::MissingVersion));
459 assert_eq!(
460 GlobalId::parse_values(&[1, 999]),
461 Err(ErrorKind::UnknownVersion(999)),
462 );
463 assert_eq!(
464 GlobalId::parse_values(&[1, 2]),
465 Err(ErrorKind::MissingNodeType),
466 );
467 assert_eq!(
468 GlobalId::parse_values(&[1, 2, 99999]),
469 Err(ErrorKind::UnknownNodeType(99999)),
470 );
471 assert_eq!(
472 GlobalId::parse_values(&[1, 2, 1]),
473 Err(ErrorKind::MissingDatabaseId),
474 );
475 assert_eq!(
476 GlobalId::parse_values(&[1, 2, 1, 1]),
477 Ok(GlobalId {
478 kind: NodeKind::SocialAuth,
479 database_id: 1,
480 }),
481 );
482 }
483}