1use std::any::Any;
98use std::collections::HashMap;
99use std::ops::Index;
100use std::ops::IndexMut;
101use std::sync::LazyLock;
102
103use chrono::DateTime;
104use chrono::Utc;
105use erased_serde::Deserializer as ErasedDeserializer;
106use erased_serde::Serialize as ErasedSerialize;
107use serde::Deserialize;
108use serde::Deserializer;
109use serde::Serialize;
110use serde::Serializer;
111use serde::de::DeserializeOwned;
112use serde::de::MapAccess;
113use serde::de::Visitor;
114use serde::ser::SerializeMap;
115
116use crate::data::Named;
117
118#[doc(hidden)]
122pub struct AttrKeyInfo {
123 pub name: &'static str,
125 pub typehash: fn() -> u64,
127 pub deserialize_erased:
129 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
130 pub meta: &'static LazyLock<Attrs>,
132 pub display: fn(&dyn SerializableValue) -> String,
134 pub parse: fn(&str) -> Result<Box<dyn SerializableValue>, anyhow::Error>,
136 pub default: Option<&'static dyn SerializableValue>,
138 pub erased: &'static dyn ErasedKey,
141}
142
143inventory::collect!(AttrKeyInfo);
144
145pub struct Key<T: 'static> {
151 name: &'static str,
152 default_value: Option<&'static T>,
153 attrs: &'static LazyLock<Attrs>,
154}
155
156impl<T: Named + 'static> Key<T> {
157 pub const fn new(
159 name: &'static str,
160 default_value: Option<&'static T>,
161 attrs: &'static LazyLock<Attrs>,
162 ) -> Self {
163 Self {
164 name,
165 default_value,
166 attrs,
167 }
168 }
169
170 pub fn name(&self) -> &'static str {
172 self.name
173 }
174
175 pub fn default(&self) -> Option<&'static T> {
177 self.default_value
178 }
179
180 pub fn has_default(&self) -> bool {
182 self.default_value.is_some()
183 }
184
185 pub fn typehash(&self) -> u64 {
187 T::typehash()
188 }
189
190 pub fn attrs(&self) -> &'static LazyLock<Attrs> {
192 self.attrs
193 }
194}
195
196impl<T: 'static> Clone for Key<T> {
197 fn clone(&self) -> Self {
198 *self
200 }
201}
202
203impl<T: 'static> Copy for Key<T> {}
204
205pub trait ErasedKey: Any + Send + Sync + 'static {
207 fn name(&self) -> &'static str;
209
210 fn typehash(&self) -> u64;
212
213 fn typename(&self) -> &'static str;
215}
216
217impl dyn ErasedKey {
218 pub fn downcast_ref<T: Named + 'static>(&'static self) -> Option<&'static Key<T>> {
220 (self as &dyn Any).downcast_ref::<Key<T>>()
221 }
222}
223
224impl<T: AttrValue> ErasedKey for Key<T> {
225 fn name(&self) -> &'static str {
226 self.name
227 }
228
229 fn typehash(&self) -> u64 {
230 T::typehash()
231 }
232
233 fn typename(&self) -> &'static str {
234 T::typename()
235 }
236}
237
238impl<T: AttrValue> Index<Key<T>> for Attrs {
240 type Output = T;
241
242 fn index(&self, key: Key<T>) -> &Self::Output {
243 self.get(key).unwrap()
244 }
245}
246
247impl<T: AttrValue> IndexMut<Key<T>> for Attrs {
250 fn index_mut(&mut self, key: Key<T>) -> &mut Self::Output {
251 self.get_mut(key).unwrap()
252 }
253}
254
255pub trait AttrValue:
266 Named + Sized + Serialize + DeserializeOwned + Send + Sync + Clone + 'static
267{
268 fn display(&self) -> String;
271
272 fn parse(value: &str) -> Result<Self, anyhow::Error>;
274}
275
276#[macro_export]
290macro_rules! impl_attrvalue {
291 ($($ty:ty),+ $(,)?) => {
292 $(
293 impl $crate::attrs::AttrValue for $ty {
294 fn display(&self) -> String {
295 self.to_string()
296 }
297
298 fn parse(value: &str) -> Result<Self, anyhow::Error> {
299 value.parse().map_err(|e| anyhow::anyhow!("failed to parse {}: {}", stringify!($ty), e))
300 }
301 }
302 )+
303 };
304}
305
306impl_attrvalue!(
310 bool,
311 i8,
312 i16,
313 i32,
314 i64,
315 i128,
316 isize,
317 u8,
318 u16,
319 u32,
320 u64,
321 u128,
322 usize,
323 f32,
324 f64,
325 String,
326 std::net::IpAddr,
327 std::net::Ipv4Addr,
328 std::net::Ipv6Addr,
329 crate::ActorId,
330 ndslice::Shape,
331 ndslice::Point,
332);
333
334impl AttrValue for std::time::Duration {
335 fn display(&self) -> String {
336 humantime::format_duration(*self).to_string()
337 }
338
339 fn parse(value: &str) -> Result<Self, anyhow::Error> {
340 Ok(humantime::parse_duration(value)?)
341 }
342}
343
344impl AttrValue for std::time::SystemTime {
345 fn display(&self) -> String {
346 let datetime: DateTime<Utc> = (*self).into();
347 datetime.to_rfc3339()
348 }
349
350 fn parse(value: &str) -> Result<Self, anyhow::Error> {
351 let datetime = DateTime::parse_from_rfc3339(value)?;
352 Ok(datetime.into())
353 }
354}
355
356#[doc(hidden)]
358pub trait SerializableValue: Send + Sync {
359 fn as_any(&self) -> &dyn Any;
361 fn as_any_mut(&mut self) -> &mut dyn Any;
363 fn as_erased_serialize(&self) -> &dyn ErasedSerialize;
365 fn cloned(&self) -> Box<dyn SerializableValue>;
367 fn display(&self) -> String;
369}
370
371impl<T: AttrValue> SerializableValue for T {
372 fn as_any(&self) -> &dyn Any {
373 self
374 }
375
376 fn as_any_mut(&mut self) -> &mut dyn Any {
377 self
378 }
379
380 fn as_erased_serialize(&self) -> &dyn ErasedSerialize {
381 self
382 }
383
384 fn cloned(&self) -> Box<dyn SerializableValue> {
385 Box::new(self.clone())
386 }
387
388 fn display(&self) -> String {
389 self.display()
390 }
391}
392
393pub struct Attrs {
411 values: HashMap<&'static str, Box<dyn SerializableValue>>,
412}
413
414impl Attrs {
415 pub fn new() -> Self {
417 Self {
418 values: HashMap::new(),
419 }
420 }
421
422 pub fn set<T: AttrValue>(&mut self, key: Key<T>, value: T) {
424 self.values.insert(key.name, Box::new(value));
425 }
426
427 fn maybe_set_from_default<T: AttrValue>(&mut self, key: Key<T>) {
428 if self.contains_key(key) {
429 return;
430 }
431 let Some(default) = key.default() else { return };
432 self.set(key, default.clone());
433 }
434
435 pub fn get<T: AttrValue>(&self, key: Key<T>) -> Option<&T> {
438 self.values
439 .get(key.name)
440 .and_then(|value| value.as_any().downcast_ref::<T>())
441 .or_else(|| key.default())
442 }
443
444 pub fn get_mut<T: AttrValue>(&mut self, key: Key<T>) -> Option<&mut T> {
447 self.maybe_set_from_default(key);
448 self.values
449 .get_mut(key.name)
450 .and_then(|value| value.as_any_mut().downcast_mut::<T>())
451 }
452
453 pub fn remove<T: AttrValue>(&mut self, key: Key<T>) -> bool {
455 self.values.remove(key.name).is_some()
457 }
458
459 pub fn contains_key<T: AttrValue>(&self, key: Key<T>) -> bool {
461 self.values.contains_key(key.name)
462 }
463
464 pub fn len(&self) -> usize {
466 self.values.len()
467 }
468
469 pub fn is_empty(&self) -> bool {
471 self.values.is_empty()
472 }
473
474 pub fn clear(&mut self) {
476 self.values.clear();
477 }
478
479 pub(crate) fn remove_value<T: 'static>(
482 &mut self,
483 key: Key<T>,
484 ) -> Option<Box<dyn SerializableValue>> {
485 self.values.remove(key.name)
486 }
487
488 pub(crate) fn insert_value<T: 'static>(
490 &mut self,
491 key: Key<T>,
492 value: Box<dyn SerializableValue>,
493 ) {
494 self.values.insert(key.name, value);
495 }
496
497 pub(crate) fn insert_value_by_name_unchecked(
499 &mut self,
500 name: &'static str,
501 value: Box<dyn SerializableValue>,
502 ) {
503 self.values.insert(name, value);
504 }
505
506 pub(crate) fn get_value_by_name(&self, name: &'static str) -> Option<&dyn SerializableValue> {
509 self.values.get(name).map(|b| b.as_ref())
510 }
511}
512
513impl Clone for Attrs {
514 fn clone(&self) -> Self {
515 let mut values = HashMap::new();
516 for (key, value) in &self.values {
517 values.insert(*key, value.cloned());
518 }
519 Self { values }
520 }
521}
522
523impl std::fmt::Display for Attrs {
524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525 let mut first = true;
526 for (key, value) in &self.values {
527 if first {
528 first = false;
529 } else {
530 write!(f, ",")?;
531 }
532 write!(f, "{}={}", key, value.display())?
533 }
534 Ok(())
535 }
536}
537
538impl std::fmt::Debug for Attrs {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 let mut debug_map = std::collections::BTreeMap::new();
542 for (key, value) in &self.values {
543 match serde_json::to_string(value.as_erased_serialize()) {
544 Ok(json) => {
545 debug_map.insert(*key, json);
546 }
547 Err(_) => {
548 debug_map.insert(*key, "<serialization error>".to_string());
549 }
550 }
551 }
552
553 f.debug_struct("Attrs").field("values", &debug_map).finish()
554 }
555}
556
557impl Default for Attrs {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563impl Serialize for Attrs {
564 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
565 where
566 S: Serializer,
567 {
568 let mut map = serializer.serialize_map(Some(self.values.len()))?;
569
570 for (key_name, value) in &self.values {
571 map.serialize_entry(key_name, value.as_erased_serialize())?;
572 }
573
574 map.end()
575 }
576}
577
578struct AttrsVisitor;
579
580impl<'de> Visitor<'de> for AttrsVisitor {
581 type Value = Attrs;
582
583 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
584 formatter.write_str("a map of attribute keys to their serialized values")
585 }
586
587 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
588 where
589 M: MapAccess<'de>,
590 {
591 static KEYS_BY_NAME: std::sync::LazyLock<HashMap<&'static str, &'static AttrKeyInfo>> =
592 std::sync::LazyLock::new(|| {
593 inventory::iter::<AttrKeyInfo>()
594 .map(|info| (info.name, info))
595 .collect()
596 });
597 let keys_by_name = &*KEYS_BY_NAME;
598
599 let mut attrs = Attrs::new();
600 while let Some(key_name) = access.next_key::<String>()? {
601 let Some(&key) = keys_by_name.get(key_name.as_str()) else {
602 access.next_value::<serde::de::IgnoredAny>()?;
604 continue;
605 };
606
607 let seed = ValueDeserializeSeed {
609 deserialize_erased: key.deserialize_erased,
610 };
611 match access.next_value_seed(seed) {
612 Ok(value) => {
613 attrs.values.insert(key.name, value);
614 }
615 Err(err) => {
616 return Err(serde::de::Error::custom(format!(
617 "failed to deserialize value for key {}: {}",
618 key_name, err
619 )));
620 }
621 }
622 }
623
624 Ok(attrs)
625 }
626}
627
628struct ValueDeserializeSeed {
630 deserialize_erased:
631 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
632}
633
634impl<'de> serde::de::DeserializeSeed<'de> for ValueDeserializeSeed {
635 type Value = Box<dyn SerializableValue>;
636
637 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
638 where
639 D: serde::de::Deserializer<'de>,
640 {
641 let mut erased = <dyn erased_serde::Deserializer>::erase(deserializer);
642 (self.deserialize_erased)(&mut erased).map_err(serde::de::Error::custom)
643 }
644}
645
646impl<'de> Deserialize<'de> for Attrs {
647 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
648 where
649 D: Deserializer<'de>,
650 {
651 deserializer.deserialize_map(AttrsVisitor)
652 }
653}
654
655#[doc(hidden)]
658pub const fn ascii_to_lowercase_const<const N: usize>(input: &str) -> [u8; N] {
659 let bytes = input.as_bytes();
660 let mut result = [0u8; N];
661 let mut i = 0;
662
663 while i < bytes.len() && i < N {
664 let byte = bytes[i];
665 if byte >= b'A' && byte <= b'Z' {
666 result[i] = byte + 32; } else {
668 result[i] = byte;
669 }
670 i += 1;
671 }
672
673 result
674}
675
676#[doc(hidden)]
678#[macro_export]
679macro_rules! const_ascii_lowercase {
680 ($s:expr) => {{
681 const INPUT: &str = $s;
682 const LEN: usize = INPUT.len();
683 const BYTES: [u8; LEN] = $crate::attrs::ascii_to_lowercase_const::<LEN>(INPUT);
684 unsafe { std::str::from_utf8_unchecked(&BYTES) }
686 }};
687}
688
689#[doc(hidden)]
692#[macro_export]
693macro_rules! assert_impl {
694 ($ty:ty, $trait:path) => {
695 const _: fn() = || {
696 fn check<T: $trait>() {}
697 check::<$ty>();
698 };
699 };
700}
701
702#[macro_export]
745macro_rules! declare_attrs {
746 ($(
748 $(#[$attr:meta])*
749 $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))*
750 $vis:vis attr $name:ident: $type:ty $(= $default:expr)?;
751 )*) => {
752 $(
753 $crate::declare_attrs! {
754 @single
755 $(@meta($($meta_key = $meta_value),*))*
756 $(#[$attr])* ;
757 $vis attr $name: $type $(= $default)?;
758 }
759 )*
760 };
761
762 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty = $default:expr;) => {
764 $crate::assert_impl!($type, $crate::attrs::AttrValue);
765
766 $crate::paste! {
768 static [<$name _DEFAULT>]: $type = $default;
769 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
770 std::sync::LazyLock::new(|| {
771 #[allow(unused_mut)]
772 let mut attrs = $crate::attrs::Attrs::new();
773 $($(
774 attrs.set($meta_key, $meta_value);
775 )*)*
776 attrs
777 });
778 }
779
780 $(#[$attr])*
781 $vis static $name: $crate::attrs::Key<$type> = {
782 $crate::assert_impl!($type, $crate::attrs::AttrValue);
783
784 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
785 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
786 $crate::paste! {
787 $crate::attrs::Key::new(
788 LOWER_NAME,
789 Some(&[<$name _DEFAULT>]),
790 $crate::paste! { &[<$name _META_ATTRS>] },
791 )
792 }
793 };
794
795 $crate::submit! {
797 $crate::attrs::AttrKeyInfo {
798 name: {
799 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
800 $crate::const_ascii_lowercase!(FULL_NAME)
801 },
802 typehash: <$type as $crate::data::Named>::typehash,
803 deserialize_erased: |deserializer| {
804 let value: $type = erased_serde::deserialize(deserializer)?;
805 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
806 },
807 meta: $crate::paste! { &[<$name _META_ATTRS>] },
808 display: |value: &dyn $crate::attrs::SerializableValue| {
809 let value = value.as_any().downcast_ref::<$type>().unwrap();
810 $crate::attrs::AttrValue::display(value)
811 },
812 parse: |value: &str| {
813 let value: $type = $crate::attrs::AttrValue::parse(value)?;
814 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
815 },
816 default: Some($crate::paste! { &[<$name _DEFAULT>] }),
817 erased: &$name,
818 }
819 }
820 };
821
822 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty;) => {
824 $crate::assert_impl!($type, $crate::attrs::AttrValue);
825
826 $crate::paste! {
827 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
828 std::sync::LazyLock::new(|| {
829 #[allow(unused_mut)]
830 let mut attrs = $crate::attrs::Attrs::new();
831 $($(
832 attrs.set($meta_key, $meta_value);
835 )*)*
836 attrs
837 });
838 }
839
840 $(#[$attr])*
841 $vis static $name: $crate::attrs::Key<$type> = {
842 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
843 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
844 $crate::attrs::Key::new(LOWER_NAME, None, $crate::paste! { &[<$name _META_ATTRS>] })
845 };
846
847
848 $crate::submit! {
850 $crate::attrs::AttrKeyInfo {
851 name: {
852 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
853 $crate::const_ascii_lowercase!(FULL_NAME)
854 },
855 typehash: <$type as $crate::data::Named>::typehash,
856 deserialize_erased: |deserializer| {
857 let value: $type = erased_serde::deserialize(deserializer)?;
858 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
859 },
860 meta: $crate::paste! { &[<$name _META_ATTRS>] },
861 display: |value: &dyn $crate::attrs::SerializableValue| {
862 let value = value.as_any().downcast_ref::<$type>().unwrap();
863 $crate::attrs::AttrValue::display(value)
864 },
865 parse: |value: &str| {
866 let value: $type = $crate::attrs::AttrValue::parse(value)?;
867 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
868 },
869 default: None,
870 erased: &$name,
871 }
872 }
873 };
874}
875
876pub use declare_attrs;
877
878#[cfg(test)]
879mod tests {
880 use std::time::Duration;
881
882 use super::*;
883
884 declare_attrs! {
885 attr TEST_TIMEOUT: Duration;
886 attr TEST_COUNT: u32;
887 @meta(TEST_COUNT = 42)
888 pub attr TEST_NAME: String;
889 }
890
891 #[test]
892 fn test_basic_operations() {
893 let mut attrs = Attrs::new();
894
895 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
897 attrs.set(TEST_COUNT, 42u32);
898 attrs.set(TEST_NAME, "test".to_string());
899
900 assert_eq!(attrs.get(TEST_TIMEOUT), Some(&Duration::from_secs(5)));
901 assert_eq!(attrs.get(TEST_COUNT), Some(&42u32));
902 assert_eq!(attrs.get(TEST_NAME), Some(&"test".to_string()));
903
904 assert!(attrs.contains_key(TEST_TIMEOUT));
906 assert!(attrs.contains_key(TEST_COUNT));
907 assert!(attrs.contains_key(TEST_NAME));
908
909 assert_eq!(attrs.len(), 3);
911 assert!(!attrs.is_empty());
912
913 assert_eq!(TEST_NAME.attrs().get(TEST_COUNT).unwrap(), &42u32);
915 }
916
917 #[test]
918 fn test_get_mut() {
919 let mut attrs = Attrs::new();
920 attrs.set(TEST_COUNT, 10u32);
921
922 if let Some(count) = attrs.get_mut(TEST_COUNT) {
923 *count += 5;
924 }
925
926 assert_eq!(attrs.get(TEST_COUNT), Some(&15u32));
927 }
928
929 #[test]
930 fn test_remove() {
931 let mut attrs = Attrs::new();
932 attrs.set(TEST_COUNT, 42u32);
933
934 let removed = attrs.remove(TEST_COUNT);
935 assert!(removed);
936 assert_eq!(attrs.get(TEST_COUNT), None);
937 assert!(!attrs.contains_key(TEST_COUNT));
938 }
939
940 #[test]
941 fn test_clear() {
942 let mut attrs = Attrs::new();
943 attrs.set(TEST_TIMEOUT, Duration::from_secs(1));
944 attrs.set(TEST_COUNT, 42u32);
945
946 attrs.clear();
947 assert!(attrs.is_empty());
948 assert_eq!(attrs.len(), 0);
949 }
950
951 #[test]
952 fn test_key_properties() {
953 assert_eq!(
954 TEST_TIMEOUT.name(),
955 "hyperactor::attrs::tests::test_timeout"
956 );
957 }
958
959 #[test]
960 fn test_serialization() {
961 let mut attrs = Attrs::new();
962 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
963 attrs.set(TEST_COUNT, 42u32);
964 attrs.set(TEST_NAME, "test".to_string());
965
966 let serialized = serde_json::to_string(&attrs).expect("Failed to serialize");
968
969 assert!(serialized.contains("hyperactor::attrs::tests::test_timeout"));
971 assert!(serialized.contains("hyperactor::attrs::tests::test_count"));
972 assert!(serialized.contains("hyperactor::attrs::tests::test_name"));
973 }
974
975 #[test]
976 fn test_deserialization() {
977 let mut original_attrs = Attrs::new();
979 original_attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
980 original_attrs.set(TEST_COUNT, 42u32);
981 original_attrs.set(TEST_NAME, "test".to_string());
982
983 let serialized = serde_json::to_string(&original_attrs).expect("Failed to serialize");
985
986 let deserialized_attrs: Attrs =
988 serde_json::from_str(&serialized).expect("Failed to deserialize");
989
990 assert_eq!(
992 deserialized_attrs.get(TEST_TIMEOUT),
993 Some(&Duration::from_secs(5))
994 );
995 assert_eq!(deserialized_attrs.get(TEST_COUNT), Some(&42u32));
996 assert_eq!(deserialized_attrs.get(TEST_NAME), Some(&"test".to_string()));
997 }
998
999 #[test]
1000 fn test_roundtrip_serialization() {
1001 let mut original = Attrs::new();
1003 original.set(TEST_TIMEOUT, Duration::from_secs(10));
1004 original.set(TEST_COUNT, 5u32);
1005 original.set(TEST_NAME, "test-service".to_string());
1006
1007 let serialized = serde_json::to_string(&original).unwrap();
1009
1010 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1012
1013 assert_eq!(
1015 deserialized.get(TEST_TIMEOUT),
1016 Some(&Duration::from_secs(10))
1017 );
1018 assert_eq!(deserialized.get(TEST_COUNT), Some(&5u32));
1019 assert_eq!(
1020 deserialized.get(TEST_NAME),
1021 Some(&"test-service".to_string())
1022 );
1023 }
1024
1025 #[test]
1026 fn test_empty_attrs_serialization() {
1027 let attrs = Attrs::new();
1028 let serialized = serde_json::to_string(&attrs).unwrap();
1029
1030 assert_eq!(serialized, "{}");
1032
1033 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1034
1035 assert!(deserialized.is_empty());
1036 }
1037
1038 #[test]
1039 fn test_format_independence() {
1040 let mut attrs = Attrs::new();
1042 attrs.set(TEST_COUNT, 42u32);
1043 attrs.set(TEST_NAME, "test".to_string());
1044
1045 let json_output = serde_json::to_string(&attrs).unwrap();
1047 let yaml_output = serde_yaml::to_string(&attrs).unwrap();
1048
1049 assert!(json_output.contains(":"));
1051 assert!(json_output.contains("\""));
1052
1053 assert!(json_output.contains("42"));
1055 assert!(!json_output.contains("\"42\""));
1056
1057 assert!(yaml_output.contains(":"));
1059 assert!(yaml_output.contains("42"));
1060
1061 assert!(!yaml_output.contains("\"42\""));
1063
1064 assert_ne!(json_output, yaml_output);
1066
1067 let from_json: Attrs = serde_json::from_str(&json_output).unwrap();
1069 let from_yaml: Attrs = serde_yaml::from_str(&yaml_output).unwrap();
1070
1071 assert_eq!(from_json.get(TEST_COUNT), Some(&42u32));
1072 assert_eq!(from_yaml.get(TEST_COUNT), Some(&42u32));
1073 assert_eq!(from_json.get(TEST_NAME), Some(&"test".to_string()));
1074 assert_eq!(from_yaml.get(TEST_NAME), Some(&"test".to_string()));
1075 }
1076
1077 #[test]
1078 fn test_clone() {
1079 let mut original = Attrs::new();
1081 original.set(TEST_COUNT, 42u32);
1082 original.set(TEST_NAME, "test".to_string());
1083 original.set(TEST_TIMEOUT, std::time::Duration::from_secs(10));
1084
1085 let cloned = original.clone();
1087
1088 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32));
1090 assert_eq!(cloned.get(TEST_NAME), Some(&"test".to_string()));
1091 assert_eq!(
1092 cloned.get(TEST_TIMEOUT),
1093 Some(&std::time::Duration::from_secs(10))
1094 );
1095
1096 original.set(TEST_COUNT, 100u32);
1098 assert_eq!(original.get(TEST_COUNT), Some(&100u32));
1099 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32)); let mut cloned_mut = cloned.clone();
1103 cloned_mut.set(TEST_NAME, "modified".to_string());
1104 assert_eq!(cloned_mut.get(TEST_NAME), Some(&"modified".to_string()));
1105 assert_eq!(original.get(TEST_NAME), Some(&"test".to_string())); }
1107
1108 #[test]
1109 fn test_debug_with_json() {
1110 let mut attrs = Attrs::new();
1111 attrs.set(TEST_COUNT, 42u32);
1112 attrs.set(TEST_NAME, "test".to_string());
1113
1114 let debug_output = format!("{:?}", attrs);
1116
1117 assert!(debug_output.contains("Attrs"));
1119
1120 assert!(debug_output.contains("42"));
1122
1123 assert!(debug_output.contains("hyperactor::attrs::tests::test_count"));
1125 assert!(debug_output.contains("hyperactor::attrs::tests::test_name"));
1126
1127 assert!(debug_output.contains("test"));
1130 }
1131
1132 declare_attrs! {
1133 attr TIMEOUT_WITH_DEFAULT: Duration = Duration::from_secs(10);
1135
1136 pub(crate) attr CRATE_LOCAL_ATTR: String;
1138 }
1139
1140 #[test]
1141 fn test_defaults() {
1142 assert!(TIMEOUT_WITH_DEFAULT.has_default());
1143 assert!(!CRATE_LOCAL_ATTR.has_default());
1144
1145 assert_eq!(
1146 Attrs::new().get(TIMEOUT_WITH_DEFAULT),
1147 Some(&Duration::from_secs(10))
1148 );
1149 }
1150
1151 #[test]
1152 fn test_indexing() {
1153 let mut attrs = Attrs::new();
1154
1155 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(10));
1156 attrs[TIMEOUT_WITH_DEFAULT] = Duration::from_secs(100);
1157 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(100));
1158
1159 attrs.set(CRATE_LOCAL_ATTR, "test".to_string());
1160 assert_eq!(attrs[CRATE_LOCAL_ATTR], "test".to_string());
1161 }
1162}