Skip to main content

aion_context/
types.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Type-safe domain identifiers for AION v2
3//!
4//! This module provides newtype wrappers around primitive types to prevent
5//! parameter confusion and provide compile-time type safety. Following Tiger Style,
6//! all types avoid panics and provide comprehensive error handling.
7//!
8//! # Type Safety Benefits
9//!
10//! Using newtypes prevents common errors:
11//!
12//! ```compile_fail
13//! # use aion_context::types::{FileId, AuthorId};
14//! fn process_file(file_id: FileId, author_id: AuthorId) { }
15//!
16//! let file = FileId::new(1);
17//! let author = AuthorId::new(1);
18//!
19//! // This won't compile - parameters are in wrong order!
20//! process_file(author, file);
21//! ```
22//!
23//! # Core Types
24//!
25//! - [`FileId`] - Unique identifier for AION files (64-bit)
26//! - [`AuthorId`] - Identifier for file authors (64-bit)
27//! - [`VersionNumber`] - Monotonically increasing version counter (64-bit)
28//!
29//! # Usage Example
30//!
31//! ```
32//! use aion_context::types::{FileId, AuthorId, VersionNumber};
33//!
34//! // Create identifiers
35//! let file_id = FileId::new(42);
36//! let author_id = AuthorId::new(1001);
37//! let version = VersionNumber(1);  // Version 1 is genesis
38//!
39//! // All types are serializable
40//! let json = serde_json::to_string(&file_id).unwrap();
41//! let deserialized: FileId = serde_json::from_str(&json).unwrap();
42//! assert_eq!(file_id, deserialized);
43//!
44//! // Version arithmetic is safe
45//! let next = version.next().unwrap();
46//! assert_eq!(next, VersionNumber(2));
47//! ```
48
49use std::fmt;
50
51/// Unique file identifier (64-bit)
52///
53/// Each AION file has a unique ID that remains constant across all versions.
54///
55/// # Examples
56///
57/// ```
58/// use aion_context::types::FileId;
59///
60/// let id = FileId::new(42);
61/// assert_eq!(id.as_u64(), 42);
62///
63/// let random_id = FileId::random();
64/// assert!(random_id.as_u64() > 0);
65/// ```
66#[derive(
67    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
68)]
69pub struct FileId(pub u64);
70
71impl FileId {
72    /// Create a new `FileId` from a u64
73    #[must_use]
74    pub const fn new(id: u64) -> Self {
75        Self(id)
76    }
77
78    /// Generate a random `FileId`
79    #[must_use]
80    pub fn random() -> Self {
81        Self(rand::random())
82    }
83
84    /// Extract the inner u64 value
85    #[must_use]
86    pub const fn as_u64(self) -> u64 {
87        self.0
88    }
89}
90
91impl fmt::Display for FileId {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        write!(f, "0x{:016x}", self.0)
94    }
95}
96
97/// Author identifier
98///
99/// Identifies the author of a version entry.
100///
101/// # Examples
102///
103/// ```
104/// use aion_context::types::AuthorId;
105///
106/// let author = AuthorId::new(1);
107/// assert_eq!(author.as_u64(), 1);
108/// ```
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
110pub struct AuthorId(pub u64);
111
112impl AuthorId {
113    /// Create a new `AuthorId` from a u64
114    #[must_use]
115    pub const fn new(id: u64) -> Self {
116        Self(id)
117    }
118
119    /// Extract the inner u64 value
120    #[must_use]
121    pub const fn as_u64(self) -> u64 {
122        self.0
123    }
124}
125
126impl fmt::Display for AuthorId {
127    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128        write!(f, "{}", self.0)
129    }
130}
131
132/// Version number (monotonically increasing)
133///
134/// Version numbers start at 1 (GENESIS) and increment sequentially.
135/// Overflow protection is built-in via `checked_add`.
136///
137/// # Examples
138///
139/// ```
140/// use aion_context::types::VersionNumber;
141///
142/// let v1 = VersionNumber::GENESIS;
143/// assert_eq!(v1.as_u64(), 1);
144///
145/// let v2 = v1.next().unwrap();
146/// assert_eq!(v2.as_u64(), 2);
147/// ```
148#[derive(
149    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
150)]
151pub struct VersionNumber(pub u64);
152
153impl VersionNumber {
154    /// Genesis version (version 1)
155    pub const GENESIS: Self = Self(1);
156
157    /// Get the next version number
158    ///
159    /// # Errors
160    ///
161    /// Returns `Err` if incrementing would overflow `u64::MAX`
162    pub fn next(self) -> crate::Result<Self> {
163        self.0
164            .checked_add(1)
165            .map(Self)
166            .ok_or(crate::AionError::VersionOverflow { max: self.0 })
167    }
168
169    /// Extract the inner u64 value
170    #[must_use]
171    pub const fn as_u64(self) -> u64 {
172        self.0
173    }
174}
175
176impl fmt::Display for VersionNumber {
177    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178        write!(f, "{}", self.0)
179    }
180}
181
182/// 256-bit hash (BLAKE3)
183pub type Hash = [u8; 32];
184
185/// Ed25519 public key
186pub type PublicKey = [u8; 32];
187
188/// Ed25519 signature
189pub type Signature = [u8; 64];
190
191#[cfg(test)]
192#[allow(clippy::unwrap_used)] // Tests are allowed to panic
193mod tests {
194    use super::*;
195
196    mod file_id {
197        use super::*;
198
199        #[test]
200        fn should_create_file_id_from_u64() {
201            let id = FileId::new(42);
202            assert_eq!(id.as_u64(), 42);
203        }
204
205        #[test]
206        fn should_generate_random_file_id() {
207            let id1 = FileId::random();
208            let id2 = FileId::random();
209            // Very unlikely to be equal
210            assert_ne!(id1, id2);
211        }
212
213        #[test]
214        fn should_display_as_hex() {
215            let id = FileId::new(255);
216            assert_eq!(format!("{id}"), "0x00000000000000ff");
217        }
218
219        #[test]
220        fn should_serialize_deserialize() {
221            let id = FileId::new(12345);
222            // Test serialization - test can panic if this fails
223            let json = serde_json::to_string(&id).unwrap();
224            let deserialized: FileId = serde_json::from_str(&json).unwrap();
225            assert_eq!(id, deserialized);
226        }
227
228        #[test]
229        fn should_be_comparable() {
230            let id1 = FileId::new(1);
231            let id2 = FileId::new(2);
232            assert!(id1 < id2);
233            assert!(id2 > id1);
234        }
235
236        #[test]
237        fn should_be_hashable() {
238            use std::collections::HashSet;
239            let mut set = HashSet::new();
240            set.insert(FileId::new(1));
241            set.insert(FileId::new(2));
242            set.insert(FileId::new(1)); // Duplicate
243            assert_eq!(set.len(), 2);
244        }
245    }
246
247    mod author_id {
248        use super::*;
249
250        #[test]
251        fn should_create_author_id_from_u64() {
252            let id = AuthorId::new(100);
253            assert_eq!(id.as_u64(), 100);
254        }
255
256        #[test]
257        fn should_display_as_decimal() {
258            let id = AuthorId::new(42);
259            assert_eq!(format!("{id}"), "42");
260        }
261
262        #[test]
263        fn should_serialize_deserialize() {
264            let id = AuthorId::new(999);
265            // Test serialization - test can panic if this fails
266            let json = serde_json::to_string(&id).unwrap();
267            let deserialized: AuthorId = serde_json::from_str(&json).unwrap();
268            assert_eq!(id, deserialized);
269        }
270
271        #[test]
272        fn should_be_comparable() {
273            let id1 = AuthorId::new(1);
274            let id2 = AuthorId::new(1);
275            assert_eq!(id1, id2);
276        }
277    }
278
279    mod version_number {
280        use super::*;
281
282        #[test]
283        fn should_have_genesis_constant() {
284            assert_eq!(VersionNumber::GENESIS.as_u64(), 1);
285        }
286
287        #[test]
288        fn should_increment_version() {
289            let v1 = VersionNumber::GENESIS;
290            let v2 = v1.next().unwrap();
291            assert_eq!(v2.as_u64(), 2);
292
293            let v3 = v2.next().unwrap();
294            assert_eq!(v3.as_u64(), 3);
295        }
296
297        #[test]
298        fn should_handle_overflow() {
299            let v_max = VersionNumber(u64::MAX);
300            let result = v_max.next();
301            assert!(result.is_err());
302        }
303
304        #[test]
305        fn should_display_as_decimal() {
306            let v = VersionNumber(42);
307            assert_eq!(format!("{v}"), "42");
308        }
309
310        #[test]
311        fn should_serialize_deserialize() {
312            let v = VersionNumber(123);
313            // Test serialization - test can panic if this fails
314            let json = serde_json::to_string(&v).unwrap();
315            let deserialized: VersionNumber = serde_json::from_str(&json).unwrap();
316            assert_eq!(v, deserialized);
317        }
318
319        #[test]
320        fn should_be_ordered() {
321            let v1 = VersionNumber(1);
322            let v2 = VersionNumber(2);
323            let v3 = VersionNumber(3);
324
325            assert!(v1 < v2);
326            assert!(v2 < v3);
327            assert!(v1 < v3);
328        }
329
330        #[test]
331        fn should_sort_correctly() {
332            let mut versions = [VersionNumber(3), VersionNumber(1), VersionNumber(2)];
333            versions.sort();
334            assert_eq!(versions.first().unwrap().as_u64(), 1);
335            assert_eq!(versions.get(1).unwrap().as_u64(), 2);
336            assert_eq!(versions.get(2).unwrap().as_u64(), 3);
337        }
338    }
339
340    mod type_aliases {
341        use super::*;
342
343        #[test]
344        fn hash_should_be_32_bytes() {
345            let hash: Hash = [0u8; 32];
346            assert_eq!(hash.len(), 32);
347        }
348
349        #[test]
350        fn public_key_should_be_32_bytes() {
351            let pk: PublicKey = [0u8; 32];
352            assert_eq!(pk.len(), 32);
353        }
354
355        #[test]
356        fn signature_should_be_64_bytes() {
357            let sig: Signature = [0u8; 64];
358            assert_eq!(sig.len(), 64);
359        }
360    }
361}