grain_id/core.rs
1use core::{fmt::Display, str::FromStr};
2
3use crate::{
4 Error,
5 alphabet::{BASE, u5_to_char_lossy},
6};
7
8/// Grain id struct
9///
10/// # Examples
11#[cfg_attr(feature = "default", doc = "```rust")]
12#[cfg_attr(not(feature = "default"), doc = "```ignore")]
13/// # use grain_id::*;
14/// # fn main() -> Result<(), Error> {
15/// // Generate random value.
16/// let random = GrainId::random();
17///
18/// assert_ne!(random, GrainId::NIL);
19///
20/// // Parse from string.
21/// let from_str: GrainId = "012abcd".parse()?;
22///
23/// // Parse from integer.
24/// let from_int: GrainId = 35990925u64.try_into()?;
25///
26/// assert_eq!(from_str, from_int);
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
31pub struct GrainId(u64);
32
33impl GrainId {
34 /// The size of the integer type in bits.
35 ///
36 /// This is not equal actually stored size.
37 pub const BITS: u32 = 35;
38
39 /// The capacity value of the grain id
40 #[deprecated(since = "0.8.1")]
41 pub const CAPACITY: u64 = (BASE as u64).pow(7);
42
43 const MAX_VALUE_PLUS_ONE: u64 = (BASE as u64).pow(7);
44 const MAX_VALUE: u64 = Self::MAX_VALUE_PLUS_ONE - 1;
45
46 const fn from_u64_unchecked(value: u64) -> Self {
47 Self(value)
48 }
49
50 /// The smallest value that can be represented by [`GrainId`].
51 ///
52 /// # Examples
53 ///
54 /// ```
55 /// # use grain_id::*;
56 /// # fn main() -> Result<(), Error> {
57 /// assert_eq!(GrainId::NIL, "0000000".parse::<GrainId>()?);
58 /// assert_eq!(GrainId::NIL, GrainId::try_from(0)?);
59 /// # Ok(())
60 /// # }
61 /// ```
62 pub const NIL: Self = Self::from_u64_unchecked(0);
63
64 /// The largest value that can be represented by [`GrainId`].
65 ///
66 /// # Examples
67 ///
68 /// ```
69 /// # use grain_id::*;
70 /// # fn main() -> Result<(), Error> {
71 /// assert_eq!(GrainId::MAX, "zzzzzzz".parse::<GrainId>()?);
72 /// assert_eq!(GrainId::MAX, GrainId::try_from(GrainId::CAPACITY - 1)?);
73 /// # Ok(())
74 /// # }
75 /// ```
76 pub const MAX: Self = Self::from_u64_unchecked(Self::MAX_VALUE);
77
78 /// Test if the [`GrainId`] is nil.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// # use grain_id::*;
84 /// # fn main() -> Result<(), Error> {
85 /// assert!("0000000".parse::<GrainId>()?.is_nil());
86 /// # Ok(())
87 /// # }
88 /// ```
89 pub fn is_nil(&self) -> bool {
90 self == &Self::NIL
91 }
92
93 /// Test if the [`GrainId`] is max.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// # use grain_id::*;
99 /// # fn main() -> Result<(), Error> {
100 /// assert!("zzzzzzz".parse::<GrainId>()?.is_max());
101 /// # Ok(())
102 /// # }
103 /// ```
104 pub fn is_max(&self) -> bool {
105 self == &Self::MAX
106 }
107
108 /// "Converts an unsigned integer to [`GrainId`] by truncating bits that exceed the valid range.")]
109 ///
110 /// This is a lossy conversion that masks the input value to fit within the ID's bit limit.
111 /// If you need to detect out-of-range values, use [`TryFrom`] instead.
112 ///
113 /// # Examples
114 ///
115 /// ```
116 /// # use grain_id::*;
117 /// // Values within range are preserved
118 /// let valid_int = 123;
119 /// let id = GrainId::from_u64_lossy(valid_int);
120 /// assert_eq!(u64::from(id), valid_int);
121 ///
122 /// // values exceeding 35 bits are truncated (MSB(s) dropped
123 /// let oversized_int = valid_int + GrainId::CAPACITY;
124 /// let overflowed_id = GrainId::from_u64_lossy(oversized_int);
125 /// assert_ne!(u64::from(overflowed_id), oversized_int);
126 /// // Only lower 35 bits retained
127 /// assert_eq!(u64::from(overflowed_id), valid_int)
128 /// ```
129 pub const fn from_u64_lossy(int: u64) -> Self {
130 Self::from_u64_unchecked(int & Self::MAX_VALUE)
131 }
132
133 /// Attempts to convert a [`u64`] to [`GrainId`].
134 ///
135 ///
136 /// Return error if the value is larger than [`GrainId::MAX`].
137 /// If you don't need to detect out-of-range values, use [`from_uint_lossy`](Self::from_uint_lossy).
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// # use grain_id::*;
143 /// # fn main() -> Result<(), Error> {
144 /// assert_eq!(GrainId::from_u64(0x7FFFFFFFF)?, GrainId::MAX);
145 /// assert!(GrainId::from_u64(0x800000000).is_err());
146 /// # Ok(())
147 /// # }
148 /// ```
149 pub const fn from_u64(value: u64) -> Result<Self, crate::Error> {
150 if value < Self::MAX_VALUE_PLUS_ONE {
151 Ok(Self::from_u64_unchecked(value))
152 } else {
153 Err(Error::ValueOutOfRange(value))
154 }
155 }
156
157 /// Returns a reference of internal 64bit integer.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// # use grain_id::*;
163 /// # fn main() -> Result<(), Error> {
164 /// let id = GrainId::from_u64(0x123456789)?;
165 /// assert_eq!(id.as_u64(), &0x123456789);
166 /// # Ok(())
167 /// # }
168 /// ```
169 pub const fn as_u64(&self) -> &u64 {
170 &self.0
171 }
172 /// Returns an internal 64bit integer value.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// # use grain_id::*;
178 /// # fn main() -> Result<(), Error> {
179 /// let id = GrainId::from_u64(0x123456789)?;
180 /// assert_eq!(id.to_u64(), 0x123456789);
181 /// # Ok(())
182 /// # }
183 /// ```
184 pub const fn to_u64(self) -> u64 {
185 self.0
186 }
187
188 fn from_chars(value: [char; 7]) -> Result<Self, Error> {
189 Ok(Self::from_u64_unchecked(u5s_to_u35_unchecked(
190 chars_to_u5s(value)?,
191 )))
192 }
193
194 pub(crate) fn to_chars(self) -> [char; 7] {
195 [
196 u5_to_char_lossy((self.0 >> 30) as u8),
197 u5_to_char_lossy((self.0 >> 25) as u8),
198 u5_to_char_lossy((self.0 >> 20) as u8),
199 u5_to_char_lossy((self.0 >> 15) as u8),
200 u5_to_char_lossy((self.0 >> 10) as u8),
201 u5_to_char_lossy((self.0 >> 5) as u8),
202 u5_to_char_lossy((self.0) as u8),
203 ]
204 }
205
206 /// Creates a `GrainId` from a [`Duration`](std::time::Duration) based on decisecond precision.
207 ///
208 /// This method converts the given duration into deciseconds (0.1 second units)
209 /// and generates a `GrainId` from it. The resulting 35-bit identifier can
210 /// represent approximately 109 years worth of deciseconds.
211 ///
212 /// # Timestamp Format
213 ///
214 /// The entire 35 bits are used to represent the timestamp in deciseconds,
215 /// allowing for chronological sorting while maintaining a compact 7-character
216 /// BASE32 representation.
217 ///
218 /// # Overflow Behavior
219 ///
220 /// If the duration exceeds the maximum representable value (2^35 deciseconds,
221 /// approximately 109 years), the timestamp will wrap around. This is generally
222 /// not a concern for personal applications within a human lifespan.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// # use grain_id::*;
228 /// use std::time::Duration;
229 ///
230 /// # fn main() -> Result<(), Error> {
231 /// let duration = Duration::new(5, 730_023_852);
232 /// let id = GrainId::from_duration(duration);
233 /// assert_eq!(id, GrainId::from_u64(57)?);
234 /// # Ok(())
235 /// # }
236 /// ```
237 pub fn from_duration(duration: core::time::Duration) -> Self {
238 let decisecs = duration.as_millis() / 100;
239 Self::from_u64_lossy(decisecs as u64)
240 }
241
242 #[deprecated(since = "0.15.0", note = "Use `to_u64().to_be_bytes()` instead.")]
243 #[doc = crate::macros::doc_to_bytes!("big endian")]
244 ///
245 /// # Examples
246 ///
247 /// ```
248 /// # use grain_id::*;
249 /// # fn main() -> Result<(), Error> {
250 /// let bytes = GrainId::from_u64(0x123456789)?.to_be_bytes();
251 /// assert_eq!(bytes, [0, 0, 0, 0x01, 0x23, 0x45, 0x67, 0x89]);
252 /// # Ok(())
253 /// # }
254 /// ```
255 pub const fn to_be_bytes(self) -> [u8; 8] {
256 self.0.to_be_bytes()
257 }
258
259 #[deprecated(since = "0.15.0", note = "Use `to_u64().to_le_bytes()` instead.")]
260 #[doc = crate::macros::doc_to_bytes!("little endian")]
261 ///
262 /// # Examples
263 ///
264 /// ```
265 /// # use grain_id::*;
266 /// # fn main() -> Result<(), Error> {
267 /// let bytes = GrainId::from_u64(0x123456789)?.to_le_bytes();
268 /// assert_eq!(bytes, [0x89, 0x67, 0x45, 0x23, 0x01, 0, 0, 0]);
269 /// # Ok(())
270 /// # }
271 /// ```
272 pub const fn to_le_bytes(self) -> [u8; 8] {
273 self.0.to_le_bytes()
274 }
275
276 #[deprecated(
277 since = "0.15.0",
278 note = "Use `GrainId::from_u64(u64::from_be_bytes(bytes))?` instead."
279 )]
280 #[doc = crate::macros::doc_from_bytes!("big endian", Self::from_be_bytes_lossy)]
281 ///
282 /// # Examples
283 ///
284 /// ```
285 /// # use grain_id::*;
286 /// # fn main() -> Result<(), Error> {
287 /// let value = [0, 0, 0, 0x01, 0x23, 0x45, 0x67, 0x89];
288 /// assert_eq!(
289 /// GrainId::from_be_bytes(value)?,
290 /// GrainId::from_u64(0x123456789)?
291 /// );
292 ///
293 /// // Oversized value returns error
294 /// let oversized_value = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
295 /// assert!(GrainId::from_be_bytes(oversized_value).is_err());
296 /// # Ok(())
297 /// # }
298 /// ```
299 pub fn from_be_bytes(bytes: [u8; 8]) -> Result<Self, Error> {
300 Self::from_u64(u64::from_be_bytes(bytes))
301 }
302
303 #[deprecated(
304 since = "0.15.0",
305 note = "Use `GrainId::from_u64(u64::from_le_bytes(bytes))?` instead."
306 )]
307 #[doc = crate::macros::doc_from_bytes!("little endian", Self::from_le_bytes_lossy)]
308 ///
309 /// # Examples
310 ///
311 /// ```
312 /// # use grain_id::*;
313 /// # fn main() -> Result<(), Error> {
314 /// let value = [0x89, 0x67, 0x45, 0x23, 0x01, 0, 0, 0];
315 /// assert_eq!(
316 /// GrainId::from_le_bytes(value)?,
317 /// GrainId::from_u64(0x123456789)?
318 /// );
319 ///
320 /// // Oversized value returns error
321 /// let oversized_value = [0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01];
322 /// assert!(GrainId::from_le_bytes(oversized_value).is_err());
323 /// # Ok(())
324 /// # }
325 /// ```
326 pub fn from_le_bytes(bytes: [u8; 8]) -> Result<Self, Error> {
327 Self::from_u64(u64::from_le_bytes(bytes))
328 }
329
330 #[deprecated(
331 since = "0.15.0",
332 note = "Use `GrainId::from_u64_lossy(u64::from_be_bytes(bytes))` instead."
333 )]
334 #[doc = crate::macros::doc_from_bytes_lossy!("big endian")]
335 ///
336 /// # Examples
337 ///
338 /// ```
339 /// # use grain_id::*;
340 /// # fn main() -> Result<(), Error> {
341 ///
342 /// let value = [0, 0, 0, 0x01, 0x23, 0x45, 0x67, 0x89];
343 /// assert_eq!(
344 /// GrainId::from_be_bytes_lossy(value),
345 /// GrainId::from_u64(0x123456789)?
346 /// );
347 ///
348 /// // Oversized value will be truncated.
349 /// let oversized_value = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
350 /// assert_eq!(
351 /// GrainId::from_be_bytes_lossy(oversized_value),
352 /// GrainId::from_u64(0x789abcdef)?
353 /// );
354 /// # Ok(())
355 /// # }
356 /// ```
357 pub fn from_be_bytes_lossy(bytes: [u8; 8]) -> Self {
358 Self::from_u64_lossy(u64::from_be_bytes(bytes))
359 }
360 #[deprecated(
361 since = "0.15.0",
362 note = "Use `GrainId::from_u64_lossy(u64::from_le_bytes(bytes))` instead."
363 )]
364 #[doc = crate::macros::doc_from_bytes_lossy!("little endian")]
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// # use grain_id::*;
370 /// # fn main() -> Result<(), Error> {
371 /// let value = [0x89, 0x67, 0x45, 0x23, 0x01, 0, 0, 0];
372 /// assert_eq!(
373 /// GrainId::from_le_bytes_lossy(value),
374 /// GrainId::from_u64(0x123456789)?
375 /// );
376 ///
377 /// // Oversized value will be truncated.
378 /// let oversized_value = [0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01];
379 /// assert_eq!(
380 /// GrainId::from_le_bytes_lossy(oversized_value),
381 /// GrainId::from_u64(0x789abcdef)?
382 /// );
383 /// # Ok(())
384 /// # }
385 /// ```
386 pub fn from_le_bytes_lossy(bytes: [u8; 8]) -> Self {
387 Self::from_u64_lossy(u64::from_le_bytes(bytes))
388 }
389
390 #[deprecated(since = "0.15.0", note = "Use `to_byte_suffix()` instead.")]
391 #[doc = crate::macros::doc_to_bytes!("big endian", compact)]
392 ///
393 /// # Examples
394 ///
395 /// ```
396 /// # use grain_id::*;
397 /// # fn main() -> Result<(), Error> {
398 /// let bytes = GrainId::from_u64(0x123456789)?.to_be_bytes_compact();
399 /// assert_eq!(bytes, [0x01, 0x23, 0x45, 0x67, 0x89]);
400 /// # Ok(())
401 /// # }
402 /// ```
403 pub const fn to_be_bytes_compact(self) -> [u8; 5] {
404 let bytes = self.0.to_be_bytes();
405 [bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]]
406 }
407
408 #[deprecated(
409 since = "0.15.0",
410 note = "Use `to_u64().to_le_bytes()` for an 8-byte little-endian representation."
411 )]
412 #[doc = crate::macros::doc_to_bytes!("little endian", compact)]
413 ///
414 /// # Examples
415 ///
416 /// ```
417 /// # use grain_id::*;
418 /// # fn main() -> Result<(), Error> {
419 /// let bytes = GrainId::from_u64(0x123456789)?.to_le_bytes_compact();
420 /// assert_eq!(bytes, [0x89, 0x67, 0x45, 0x23, 0x01]);
421 /// # Ok(())
422 /// # }
423 /// ```
424 pub const fn to_le_bytes_compact(self) -> [u8; 5] {
425 let bytes = self.0.to_le_bytes();
426 [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]]
427 }
428
429 #[deprecated(
430 since = "0.15.0",
431 note = "Use `from_byte_suffix()` for lossy conversion, or `GrainId::from_u64(u64::from_be_bytes([0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]]))?` for checked conversion."
432 )]
433 #[doc = crate::macros::doc_from_bytes!("big endian", compact)]
434 ///
435 /// # Examples
436 ///
437 /// ```
438 /// # use grain_id::*;
439 /// # fn main() -> Result<(), Error> {
440 /// let value = [0x01, 0x23, 0x45, 0x67, 0x89];
441 /// assert_eq!(
442 /// GrainId::from_be_bytes_compact(value)?,
443 /// GrainId::from_u64(0x123456789)?
444 /// );
445 ///
446 /// // Overflowed value returns error.
447 /// let oversized_value = [0x12, 0x34, 0x56, 0x78, 0x90];
448 /// assert!(GrainId::from_be_bytes_compact(oversized_value).is_err());
449 /// # Ok(())
450 /// # }
451 /// ```
452 pub fn from_be_bytes_compact(bytes: [u8; 5]) -> Result<Self, Error> {
453 Self::from_u64(u64::from_be_bytes([
454 0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4],
455 ]))
456 }
457
458 #[deprecated(
459 since = "0.15.0",
460 note = "Use `GrainId::from_u64(u64::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], 0, 0, 0]))?` instead."
461 )]
462 #[doc = crate::macros::doc_from_bytes!("little endian", compact)]
463 ///
464 /// # Examples
465 ///
466 /// ```
467 /// # use grain_id::*;
468 /// # fn main() -> Result<(), Error> {
469 /// let value = [0x89, 0x67, 0x45, 0x23, 0x01];
470 /// assert_eq!(
471 /// GrainId::from_le_bytes_compact(value)?,
472 /// GrainId::from_u64(0x123456789)?
473 /// );
474 ///
475 /// // Oversized value returns error.
476 /// let oversized_value = [0x90, 0x78, 0x56, 0x34, 0x12];
477 /// assert!(GrainId::from_le_bytes_compact(oversized_value).is_err());
478 /// # Ok(())
479 /// # }
480 /// ```
481 pub fn from_le_bytes_compact(bytes: [u8; 5]) -> Result<Self, Error> {
482 Self::from_u64(u64::from_le_bytes([
483 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], 0, 0, 0,
484 ]))
485 }
486
487 #[deprecated(since = "0.15.0", note = "Use `from_byte_suffix()` instead.")]
488 #[doc = crate::macros::doc_from_bytes_lossy!("big endian", compact)]
489 ///
490 /// # Examples
491 ///
492 /// ```
493 /// # use grain_id::*;
494 /// # fn main() -> Result<(), Error> {
495 /// let value = [0x01, 0x23, 0x45, 0x67, 0x89];
496 /// assert_eq!(
497 /// GrainId::from_be_bytes_compact_lossy(value),
498 /// GrainId::from_u64(0x123456789)?
499 /// );
500 ///
501 /// // Oversized value will be truncated.
502 /// let oversized_value = [0x12, 0x34, 0x56, 0x78, 0x90];
503 /// assert_eq!(
504 /// GrainId::from_be_bytes_compact_lossy(oversized_value),
505 /// GrainId::from_u64(0x234567890)?
506 /// );
507 /// # Ok(())
508 /// # }
509 /// ```
510 pub fn from_be_bytes_compact_lossy(bytes: [u8; 5]) -> Self {
511 Self::from_u64_lossy(u64::from_be_bytes([
512 0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4],
513 ]))
514 }
515
516 #[deprecated(
517 since = "0.15.0",
518 note = "Use `GrainId::from_u64_lossy(u64::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], 0, 0, 0]))` instead."
519 )]
520 #[doc = crate::macros::doc_from_bytes_lossy!("little endian", compact)]
521 ///
522 /// # Examples
523 ///
524 /// ```
525 /// # use grain_id::*;
526 /// # fn main() -> Result<(), Error> {
527 /// let value = [0x89, 0x67, 0x45, 0x23, 0x01];
528 /// assert_eq!(
529 /// GrainId::from_le_bytes_compact_lossy(value),
530 /// GrainId::from_u64(0x123456789)?
531 /// );
532 ///
533 /// // Oversized value will be truncated
534 /// let oversized_value = [0x90, 0x78, 0x56, 0x34, 0x12];
535 /// assert_eq!(
536 /// GrainId::from_le_bytes_compact_lossy(oversized_value),
537 /// GrainId::from_u64(0x234567890)?
538 /// );
539 /// # Ok(())
540 /// # }
541 /// ```
542 pub fn from_le_bytes_compact_lossy(bytes: [u8; 5]) -> Self {
543 Self::from_u64_lossy(u64::from_le_bytes([
544 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], 0, 0, 0,
545 ]))
546 }
547
548 /// Wrapping (modular) subtraction. Computes `self - rhs`, wrapping around at the boundary of the type.
549 ///
550 /// # Examples
551 /// ```
552 /// # use grain_id::*;
553 /// # fn main() -> Result<(), Error> {
554 /// assert_eq!(
555 /// GrainId::from_u64(100)?.wrapping_sub(GrainId::from_u64(100)?),
556 /// GrainId::NIL
557 /// );
558 /// assert_eq!(
559 /// GrainId::from_u64(100)?.wrapping_sub(GrainId::from_u64(101)?),
560 /// GrainId::MAX
561 /// );
562 /// # Ok(())
563 /// # }
564 /// ```
565 pub const fn wrapping_sub(self, rhs: Self) -> Self {
566 GrainId::from_u64_lossy(self.to_u64().wrapping_sub(rhs.to_u64()))
567 }
568
569 /// Creates a `GrainId` from the leading (most significant) 35 bits of a 5-byte array.
570 ///
571 /// This is useful for generating a compact identifier from a pre-computed hash,
572 /// public key, or any arbitrary byte sequence.
573 ///
574 /// # Examples
575 ///
576 /// ```
577 /// # use grain_id::*;
578 /// // Extract leading 35 bits from a byte array
579 /// let bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
580 /// let id = GrainId::from_byte_prefix(&bytes);
581 /// assert_eq!(id, GrainId::MAX);
582 ///
583 /// // For longer data, slice the first 5 bytes
584 /// let hash: [u8; 32] = [0; 32];
585 /// let id = GrainId::from_byte_prefix(hash[..5].try_into().unwrap());
586 /// assert_eq!(id, GrainId::NIL);
587 /// ```
588 pub const fn from_byte_prefix(bytes: &[u8; 5]) -> Self {
589 let value = (bytes[0] as u64) << 27
590 | (bytes[1] as u64) << 19
591 | (bytes[2] as u64) << 11
592 | (bytes[3] as u64) << 3
593 | (bytes[4] as u64) >> 5;
594 Self::from_u64_lossy(value)
595 }
596
597 /// Creates a `GrainId` from the trailing (least significant) 35 bits of a 5-byte array.
598 ///
599 /// This is the counterpart to [`from_byte_prefix`](Self::from_byte_prefix).
600 /// The upper 5 bits of the first byte are ignored.
601 ///
602 /// # Examples
603 ///
604 /// ```
605 /// # use grain_id::*;
606 /// // Extract trailing 35 bits from a byte array
607 /// let bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
608 /// let id = GrainId::from_byte_suffix(&bytes);
609 /// assert_eq!(id, GrainId::MAX);
610 ///
611 /// // Upper 5 bits of the first byte are ignored
612 /// let bytes = [0xF8, 0x00, 0x00, 0x00, 0x00];
613 /// let id = GrainId::from_byte_suffix(&bytes);
614 /// assert_eq!(id, GrainId::NIL);
615 /// ```
616 pub const fn from_byte_suffix(bytes: &[u8; 5]) -> Self {
617 let value = ((bytes[0] & 0x07) as u64) << 32
618 | (bytes[1] as u64) << 24
619 | (bytes[2] as u64) << 16
620 | (bytes[3] as u64) << 8
621 | (bytes[4] as u64);
622 Self::from_u64_unchecked(value)
623 }
624
625 /// Returns a 5-byte array with the `GrainId` value stored in the leading (most significant) 35 bits.
626 ///
627 /// This is the counterpart to [`from_byte_prefix`](Self::from_byte_prefix).
628 /// The lower 5 bits of the last byte are always zero.
629 ///
630 /// # Examples
631 ///
632 /// ```
633 /// # use grain_id::*;
634 /// # fn main() -> Result<(), Error> {
635 /// let bytes = GrainId::from_u64(0x123456789)?.to_byte_prefix();
636 /// assert_eq!(bytes, [0x24, 0x68, 0xAC, 0xF1, 0x20]);
637 ///
638 /// // Round-trip
639 /// let id = GrainId::from_u64(0x123456789)?;
640 /// assert_eq!(GrainId::from_byte_prefix(&id.to_byte_prefix()), id);
641 /// # Ok(())
642 /// # }
643 /// ```
644 pub const fn to_byte_prefix(self) -> [u8; 5] {
645 let v = self.0;
646 [
647 (v >> 27) as u8,
648 (v >> 19) as u8,
649 (v >> 11) as u8,
650 (v >> 3) as u8,
651 ((v & 0x07) << 5) as u8,
652 ]
653 }
654
655 /// Returns a 5-byte array with the `GrainId` value stored in the trailing (least significant) 35 bits.
656 ///
657 /// This is the counterpart to [`from_byte_suffix`](Self::from_byte_suffix).
658 /// The upper 5 bits of the first byte are always zero.
659 ///
660 /// # Examples
661 ///
662 /// ```
663 /// # use grain_id::*;
664 /// # fn main() -> Result<(), Error> {
665 /// let bytes = GrainId::from_u64(0x123456789)?.to_byte_suffix();
666 /// assert_eq!(bytes, [0x01, 0x23, 0x45, 0x67, 0x89]);
667 ///
668 /// // Round-trip
669 /// let id = GrainId::from_u64(0x123456789)?;
670 /// assert_eq!(GrainId::from_byte_suffix(&id.to_byte_suffix()), id);
671 /// # Ok(())
672 /// # }
673 /// ```
674 pub const fn to_byte_suffix(self) -> [u8; 5] {
675 let v = self.0;
676 [
677 (v >> 32) as u8,
678 (v >> 24) as u8,
679 (v >> 16) as u8,
680 (v >> 8) as u8,
681 v as u8,
682 ]
683 }
684
685 /// Increments the value by 1, wrapping around to [`GrainId::NIL`] on overflow.
686 ///
687 /// # Examples
688 /// ```
689 /// # use grain_id::*;
690 /// # fn main() -> Result<(), Error> {
691 /// assert_eq!(GrainId::from_u64(100)?.increment(), GrainId::from_u64(101)?);
692 /// assert_eq!(GrainId::MAX.increment(), GrainId::NIL);
693 /// # Ok(())
694 /// # }
695 /// ```
696 pub const fn increment(self) -> Self {
697 GrainId::from_u64_lossy(self.to_u64() + 1)
698 }
699}
700
701fn chars_to_u5s(value: [char; 7]) -> Result<[u8; 7], Error> {
702 let mut result = [0; 7];
703 for i in 0..7 {
704 result[i] = crate::alphabet::char_to_u5(value[i]).ok_or(Error::InvalidCharacter {
705 character: value[i],
706 index: i,
707 })?;
708 }
709 Ok(result)
710}
711
712fn u5s_to_u35_unchecked(value: [u8; 7]) -> u64 {
713 ((value[0] as u64) << 30)
714 | ((value[1] as u64) << 25)
715 | ((value[2] as u64) << 20)
716 | ((value[3] as u64) << 15)
717 | ((value[4] as u64) << 10)
718 | ((value[5] as u64) << 5)
719 | (value[6] as u64)
720}
721
722impl AsRef<u64> for GrainId {
723 fn as_ref(&self) -> &u64 {
724 self.as_u64()
725 }
726}
727
728impl TryFrom<u64> for GrainId {
729 type Error = Error;
730 /// Attempts to convert a [`u64`] to [`GrainId`].
731 ///
732 /// # Error
733 /// Return error if the value is larger than [`GrainId::MAX`].
734 /// If you don't need to detect out-of-range values, use [`from_u64_lossy`](GrainId::from_u64_lossy).
735 ///
736 /// # Examples
737 ///
738 /// ```
739 /// # use grain_id::*;
740 /// assert!(GrainId::try_from(GrainId::CAPACITY - 1).is_ok());
741 /// assert!(GrainId::try_from(GrainId::CAPACITY).is_err());
742 /// ```
743 fn try_from(value: u64) -> Result<Self, Self::Error> {
744 Self::from_u64(value)
745 }
746}
747
748impl From<GrainId> for u64 {
749 fn from(value: GrainId) -> Self {
750 value.0
751 }
752}
753
754impl Display for GrainId {
755 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
756 let chars = self.to_chars();
757 write!(
758 f,
759 "{}{}{}{}{}{}{}",
760 chars[0], chars[1], chars[2], chars[3], chars[4], chars[5], chars[6]
761 )
762 }
763}
764
765impl FromStr for GrainId {
766 type Err = Error;
767
768 fn from_str(s: &str) -> Result<Self, Self::Err> {
769 let len = s.len();
770 if len == 7 {
771 let mut chars = s.chars();
772 Self::from_chars([
773 chars.next().unwrap(),
774 chars.next().unwrap(),
775 chars.next().unwrap(),
776 chars.next().unwrap(),
777 chars.next().unwrap(),
778 chars.next().unwrap(),
779 chars.next().unwrap(),
780 ])
781 } else {
782 Err(Error::InvalidLength(len))
783 }
784 }
785}