feagi_data_structures/
common_macros.rs

1//region Index / Count
2
3/// Creates a strongly-typed index wrapper around an integer type.
4/// 
5/// # Example
6/// ```
7/// use feagi_data_structures::define_index;
8/// 
9/// define_index!(NodeId, u32, "Unique identifier for a node");
10/// 
11/// let id = NodeId::from(42);
12/// assert_eq!(*id, 42);
13/// let raw: u32 = id.into();
14/// assert_eq!(raw, 42);
15/// ```
16#[macro_export]
17macro_rules! define_index {
18    ($name:ident, $inner:ty, $doc:expr) => {
19        #[doc = $doc]
20        #[repr(transparent)]
21        #[derive(
22            Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord
23        )]
24        pub struct $name($inner);
25
26        impl $name {
27
28            // const constructor
29            pub const fn from(var: $inner) -> Self {
30                Self(var)
31            }
32
33            // const return method
34            pub const fn get(&self) -> $inner {
35                self.0
36            }
37        }
38
39        impl std::ops::Deref for $name {
40            type Target = $inner;
41
42            fn deref(&self) -> &Self::Target {
43                &self.0
44            }
45        }
46
47        impl From<$inner> for $name {
48            fn from(value: $inner) -> Self {
49                $name(value)
50            }
51        }
52
53        impl From<$name> for $inner {
54            fn from(value: $name) -> Self {
55                value.0
56            }
57        }
58
59        impl std::fmt::Display for $name {
60            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61                write!(f, "{}", self.0)
62            }
63        }
64    };
65}
66
67/// Creates a non-zero count type with validation.
68///
69/// # Example
70/// ```
71/// use feagi_data_structures::{define_nonzero_count, FeagiDataError};
72///
73/// define_nonzero_count!(ItemCount, u32, "Number of items (must be > 0)");
74///
75/// let count = ItemCount::new(5).unwrap();
76/// assert_eq!(*count, 5);
77///
78/// let invalid = ItemCount::new(0);
79/// assert!(invalid.is_err());
80/// ```
81#[macro_export]
82macro_rules! define_nonzero_count {
83    ($name:ident, $base:ty, $doc:expr) => {
84        #[doc = $doc]
85        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
86        pub struct $name {
87            value: $base,
88        }
89
90        impl $name {
91            /// Creates a new instance, returns Err if validation fails
92            pub fn new(value: $base) -> Result<Self, FeagiDataError> {
93                if value == 0 {
94                    return Err(FeagiDataError::BadParameters("Count cannot be zero!".into()));
95                }
96                Ok($name{
97                    value,
98                })
99            }
100        }
101        impl TryFrom<$base> for $name {
102            type Error = FeagiDataError;
103            fn try_from(value: $base) -> Result<Self, FeagiDataError> {
104                $name::new({value})
105            }
106        }
107
108        impl From<$name> for $base {
109            fn from(value: $name) -> $base {
110                value.value
111            }
112        }
113
114        impl std::ops::Deref for $name {
115            type Target = $base;
116            fn deref(&self) -> &Self::Target {
117                &self.value
118            }
119        }
120
121        impl std::fmt::Display for $name {
122            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123                self.value.fmt(f)
124            }
125        }
126
127    }
128}
129
130// endregion
131
132//region XY
133
134/// Creates a 2D coordinate type with x,y fields.
135/// 
136/// # Example
137/// ```
138/// use feagi_data_structures::define_xy_coordinates;
139/// 
140/// define_xy_coordinates!(Point2D, i32, "Point2D", "A 2D point with integer coordinates");
141/// 
142/// let point = Point2D::new(10, 20);
143/// assert_eq!(point.x, 10);
144/// assert_eq!(point.y, 20);
145/// println!("{}", point); // Point2D(10, 20)
146/// ```
147#[macro_export]
148macro_rules! define_xy_coordinates {
149    ($name:ident, $var_type:ty, $friendly_name:expr, $doc_string:expr) => {
150
151        #[doc = $doc_string]
152        #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
153        pub struct $name {
154            pub x: $var_type,
155            pub y: $var_type,
156        }
157
158        impl $name {
159            pub fn new(x: $var_type, y: $var_type) -> Self {
160                Self { x, y }
161            }
162        }
163
164        impl From<$name> for ($var_type, $var_type) {
165            fn from(value: $name) -> Self {
166                (value.x, value.y)
167            }
168        }
169
170        impl From<($var_type, $var_type)> for $name {
171            fn from(value: ($var_type, $var_type)) -> Self {
172                $name::new(value.0, value.1)
173            }
174        }
175
176        impl std::fmt::Display for $name {
177            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
178                write!(f, "{}({}, {})", $friendly_name, self.x, self.y)
179            }
180        }
181
182    };
183}
184
185/// Creates a 2D dimension type with width,height fields and validation.
186/// 
187/// # Example
188/// ```
189/// use feagi_data_structures::{define_xy_dimensions, FeagiDataError};
190/// 
191/// define_xy_dimensions!(Size2D, u32, "Size2D", 0, "A 2D size with positive dimensions");
192/// 
193/// let size = Size2D::new(640, 480).unwrap();
194/// assert_eq!(size.width, 640);
195/// assert_eq!(size.height, 480);
196/// 
197/// let invalid = Size2D::new(0, 480);
198/// assert!(invalid.is_err());
199/// ```
200#[macro_export]
201macro_rules! define_xy_dimensions {
202    ($name:ident, $var_type:ty, $friendly_name:expr, $invalid_zero_value:expr, $doc_string:expr) => {
203
204        #[doc = $doc_string]
205        #[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
206        pub struct $name {
207            pub width: $var_type,
208            pub height: $var_type,
209        }
210
211        impl $name {
212            pub fn new(x: $var_type, y: $var_type) -> Result<Self, FeagiDataError> {
213                if x == $invalid_zero_value || y == $invalid_zero_value {
214                    return Err(FeagiDataError::BadParameters(format!("Value cannot be {:?} in a {:?}!", $invalid_zero_value, $friendly_name)));
215                }
216                Ok(Self { width: x, height: y })
217            }
218        }
219
220        impl std::fmt::Display for $name {
221            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
222                write!(f, "{}<{}, {}>", $friendly_name, self.width, self.height)
223            }
224        }
225
226        impl From<$name> for ($var_type, $var_type) {
227            fn from(value: $name) -> Self {
228                (value.width, value.height)
229            }
230        }
231
232        impl TryFrom<($var_type, $var_type)> for $name {
233            type Error = FeagiDataError;
234            fn try_from(value: ($var_type, $var_type)) -> Result<Self, Self::Error> {
235                if value.0 == $invalid_zero_value {
236                    return Err(FeagiDataError::BadParameters(format!("X value cannot be zero!")));
237                }
238                if value.1 == $invalid_zero_value {
239                    return Err(FeagiDataError::BadParameters(format!("Y value cannot be zero!")));
240                }
241                Ok(Self { width: value.0, height: value.1 })
242            }
243        }
244
245    }
246}
247
248//endregion
249
250//region XYZ
251
252/// Creates a 3D coordinate type with x,y,z fields.
253/// 
254/// # Example
255/// ```
256/// use feagi_data_structures::define_xyz_coordinates;
257/// 
258/// define_xyz_coordinates!(Point3D, u32, "Point3D", "A 3D point with u32 coordinates");
259/// 
260/// let point = Point3D::new(1, 2, 3);
261/// assert_eq!(point.x, 1);
262/// assert_eq!(point.y, 2);
263/// assert_eq!(point.z, 3);
264/// println!("{}", point); // Point3D(1.0, 2.0, 3.0)
265/// ```
266#[macro_export]
267macro_rules! define_xyz_coordinates {
268    ($name:ident, $var_type:ty, $friendly_name:expr, $doc_string:expr) => {
269
270        #[doc = $doc_string]
271        #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
272        pub struct $name {
273            pub x: $var_type,
274            pub y: $var_type,
275            pub z: $var_type,
276        }
277
278        impl $name {
279            pub fn new(x: $var_type, y: $var_type, z: $var_type) -> Self {
280                Self { x, y, z }
281            }
282        }
283
284        impl From<$name> for ($var_type, $var_type, $var_type) {
285            fn from(value: $name) -> Self {
286                (value.x, value.y, value.z)
287            }
288        }
289
290        impl From<($var_type, $var_type, $var_type)> for $name {
291            fn from(value: ($var_type, $var_type, $var_type)) -> Self {
292                $name::new(value.0, value.1, value.2)
293            }
294        }
295
296        impl std::fmt::Display for $name {
297            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
298                write!(f, "{}({}, {}, {})", $friendly_name, self.x, self.y, self.z)
299            }
300        }
301
302    };
303}
304
305/// Creates a 3D dimension type with width,height,depth fields and validation.
306/// 
307/// # Example
308/// ```
309/// use feagi_data_structures::{define_xyz_dimensions, FeagiDataError};
310/// 
311/// define_xyz_dimensions!(Volume3D, u32, "Volume3D", 0, "A 3D volume with positive dimensions");
312/// 
313/// let vol = Volume3D::new(10, 20, 30).unwrap();
314/// assert_eq!(vol.width, 10);
315/// assert_eq!(vol.height, 20);
316/// assert_eq!(vol.depth, 30);
317/// assert_eq!(vol.number_elements(), 6000);
318/// 
319/// let invalid = Volume3D::new(0, 20, 30);
320/// assert!(invalid.is_err());
321/// ```
322#[macro_export]
323macro_rules! define_xyz_dimensions {
324    ($name:ident, $var_type:ty, $friendly_name:expr, $invalid_zero_value:expr, $doc_string:expr) => {
325
326        #[doc = $doc_string]
327        #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
328        pub struct $name {
329            pub width: $var_type,
330            pub height: $var_type,
331            pub depth: $var_type,
332        }
333
334        impl $name {
335            pub fn new(x: $var_type, y: $var_type, z: $var_type) -> Result<Self, FeagiDataError> {
336                if x == $invalid_zero_value || y == $invalid_zero_value || z == $invalid_zero_value {
337                    return Err(FeagiDataError::BadParameters(format!("Value cannot be {:?} in a {:?}!", $invalid_zero_value, $friendly_name)));
338                }
339                Ok(Self { width: x, height: y, depth: z })
340            }
341
342            pub fn number_elements(&self) -> $var_type {
343                self.width * self.height * self.depth
344            }
345        }
346
347        impl std::fmt::Display for $name {
348            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
349                write!(f, "{}<{}, {}, {}>", $friendly_name, self.width, self.height, self.depth)
350            }
351        }
352
353        impl From<$name> for ($var_type, $var_type, $var_type) {
354            fn from(value: $name) -> Self {
355                (value.width, value.height, value.depth)
356            }
357        }
358
359        impl TryFrom<($var_type, $var_type, $var_type)> for $name {
360            type Error = FeagiDataError;
361            fn try_from(value: ($var_type, $var_type, $var_type)) -> Result<Self, Self::Error> {
362                if value.0 == $invalid_zero_value {
363                    return Err(FeagiDataError::BadParameters(format!("X value cannot be zero!")));
364                }
365                if value.1 == $invalid_zero_value {
366                    return Err(FeagiDataError::BadParameters(format!("Y value cannot be zero!")));
367                }
368                if value.2 == $invalid_zero_value {
369                    return Err(FeagiDataError::BadParameters(format!("Z value cannot be zero!")));
370                }
371                Ok(Self { width: value.0, height: value.1, depth: value.2 })
372            }
373        }
374    }
375}
376
377/// Creates bidirectional conversions between two XYZ dimension types.
378/// 
379/// # Example
380/// ```
381/// use feagi_data_structures::{define_xyz_dimensions, define_xyz_mapping, FeagiDataError};
382/// 
383/// define_xyz_dimensions!(VolumeA, u32, "VolumeA", 0, "Volume type A");
384/// define_xyz_dimensions!(VolumeB, u32, "VolumeB", 0, "Volume type B");
385/// define_xyz_mapping!(VolumeA, VolumeB);
386/// 
387/// let vol_a = VolumeA::new(10, 20, 30).unwrap();
388/// let vol_b: VolumeB = vol_a.into();
389/// let back_to_a: VolumeA = vol_b.into();
390/// assert_eq!(vol_a, back_to_a);
391/// ```
392#[macro_export]
393macro_rules! define_xyz_mapping{
394    ($XYZ_a:ident, $XYZ_b:ident) => {
395        impl From<$XYZ_a> for $XYZ_b {
396            fn from(a: $XYZ_a) -> Self {
397                $XYZ_b::new(a.width, a.height, a.depth).unwrap()
398            }
399        }
400        impl From<$XYZ_b> for $XYZ_a {
401            fn from(b: $XYZ_b) -> Self {
402                $XYZ_a::new(b.width, b.height, b.depth).unwrap()
403            }
404        }
405    }
406}
407
408/// Creates a 3D dimension range type for spatial bounds checking.
409/// 
410/// # Example
411/// ```
412/// use feagi_data_structures::{define_xyz_dimensions, define_xyz_dimension_range, FeagiDataError};
413/// 
414/// define_xyz_dimensions!(Position3D, u32, "Position3D", 0, "3D position coordinates");
415/// define_xyz_dimension_range!(BoundingBox3D, u32, Position3D, "BoundingBox3D", "3D bounding box for spatial queries");
416/// 
417/// let bounds = BoundingBox3D::new(0..10, 0..20, 0..30).unwrap();
418/// let pos = Position3D::new(5, 15, 25).unwrap();
419/// assert!(bounds.verify_coordinate_within_range(&pos).is_ok());
420/// 
421/// let out_of_bounds = Position3D::new(15, 15, 25).unwrap();
422/// assert!(bounds.verify_coordinate_within_range(&out_of_bounds).is_err());
423/// ```
424#[macro_export]
425macro_rules! define_xyz_dimension_range {
426    ($name:ident, $var_type:ty, $coordinate_type:ty, $friendly_name:expr, $doc:expr) => {
427        #[doc = $doc]
428        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
429        pub struct $name {
430            pub width: std::ops::Range<$var_type>,
431            pub height: std::ops::Range<$var_type>,
432            pub depth: std::ops::Range<$var_type>
433        }
434
435        impl $name {
436            /// Creates a new dimension range, ensuring no ranges are empty.
437            pub fn new(x: std::ops::Range<$var_type>, y: std::ops::Range<$var_type>, z: std::ops::Range<$var_type>) -> Result<Self, FeagiDataError> {
438                Ok($name {width: x, height: y, depth: z})
439            }
440
441            /// Verifies that a coordinate falls within all axis ranges.
442            pub fn verify_coordinate_within_range(&self, coordinate: &$coordinate_type) -> Result<(), FeagiDataError> {
443                if self.width.contains(&coordinate.width) && self.height.contains(&coordinate.height) && self.depth.contains(&coordinate.depth) {
444                    return Ok(());
445                }
446                Err(FeagiDataError::BadParameters(format!("Coordinate {:?} is not contained by this given range of {:?}!", coordinate, self)))
447
448            }
449        }
450
451        impl std::fmt::Display for $name {
452            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
453                write!(f, "{}<{:?}, {:?}, {:?}>", $friendly_name, self.width, self.height, self.depth)
454            }
455        }
456    };
457}
458
459//endregion