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