1use std::any::Any;
98use std::collections::HashMap;
99use std::fmt::Display;
100use std::ops::Index;
101use std::ops::IndexMut;
102use std::str::FromStr;
103use std::sync::LazyLock;
104
105use chrono::DateTime;
106use chrono::Utc;
107use erased_serde::Deserializer as ErasedDeserializer;
108use erased_serde::Serialize as ErasedSerialize;
109use serde::Deserialize;
110use serde::Deserializer;
111use serde::Serialize;
112use serde::Serializer;
113use serde::de::DeserializeOwned;
114use serde::de::MapAccess;
115use serde::de::Visitor;
116use serde::ser::SerializeMap;
117use typeuri::Named;
118
119use crate::flattrs::Flattrs;
120
121#[doc(hidden)]
125pub struct AttrKeyInfo {
126 pub name: &'static str,
128 pub key_hash: u64,
130 pub typehash: fn() -> u64,
132 pub deserialize_erased:
134 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
135 pub deserialize_bincode:
137 fn(&[u8]) -> Result<Box<dyn SerializableValue>, bincode::error::DecodeError>,
138 pub meta: &'static LazyLock<Attrs>,
140 pub display: fn(&dyn SerializableValue) -> String,
142 pub parse: fn(&str) -> Result<Box<dyn SerializableValue>, anyhow::Error>,
144 pub default: Option<&'static dyn SerializableValue>,
146 pub erased: &'static dyn ErasedKey,
149}
150
151inventory::collect!(AttrKeyInfo);
152
153pub fn lookup_key_info(key_hash: u64) -> Option<&'static AttrKeyInfo> {
158 static KEYS_BY_HASH: std::sync::LazyLock<std::collections::HashMap<u64, &'static AttrKeyInfo>> =
159 std::sync::LazyLock::new(|| {
160 inventory::iter::<AttrKeyInfo>()
161 .map(|info| (info.key_hash, info))
162 .collect()
163 });
164 KEYS_BY_HASH.get(&key_hash).copied()
165}
166
167pub fn lookup_key_info_by_name(name: &str) -> Option<&'static AttrKeyInfo> {
172 static KEYS_BY_NAME: std::sync::LazyLock<
173 std::collections::HashMap<&'static str, &'static AttrKeyInfo>,
174 > = std::sync::LazyLock::new(|| {
175 inventory::iter::<AttrKeyInfo>()
176 .map(|info| (info.name, info))
177 .collect()
178 });
179 KEYS_BY_NAME.get(name).copied()
180}
181
182declare_attrs! {
183 pub attr OPERATION_CONTEXT_HEADER: bool;
195}
196
197pub fn is_attr_marked_with(name: &str, marker: Key<bool>) -> Option<bool> {
209 let info = lookup_key_info_by_name(name)?;
210 info.meta.get(marker).copied()
211}
212
213pub fn stamp_marked_attrs_into_flattrs(dst: &mut Flattrs, attrs: &Attrs, marker: Key<bool>) {
229 for (name, value) in attrs.iter() {
230 if is_attr_marked_with(name, marker) != Some(true) {
231 continue;
232 }
233 dst.set_serialized(fnv1a_hash(name.as_bytes()), &value.serialize_bincode());
234 }
235}
236
237pub fn copy_marked_flattrs(dst: &mut Flattrs, src: &Flattrs, marker: Key<bool>) {
249 for (key_hash, value) in src.iter() {
250 let Some(info) = lookup_key_info(key_hash) else {
251 continue;
252 };
253 if info.meta.get(marker).copied() != Some(true) {
254 continue;
255 }
256 dst.set_serialized(key_hash, value);
257 }
258}
259
260pub fn marked_attr_names(marker: Key<bool>) -> std::collections::HashSet<&'static str> {
269 inventory::iter::<AttrKeyInfo>()
270 .filter(|info| info.meta.get(marker).copied() == Some(true))
271 .map(|info| info.name)
272 .collect()
273}
274
275pub struct Key<T: 'static> {
281 name: &'static str,
282 default_value: Option<&'static T>,
283 attrs: &'static LazyLock<Attrs>,
284}
285
286impl<T> Key<T> {
287 pub fn name(&self) -> &'static str {
289 self.name
290 }
291
292 pub const fn key_hash(&self) -> u64 {
297 fnv1a_hash(self.name.as_bytes())
298 }
299}
300
301pub const fn fnv1a_hash(bytes: &[u8]) -> u64 {
306 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
307 const FNV_PRIME: u64 = 0x100000001b3;
308
309 let mut hash = FNV_OFFSET_BASIS;
310 let mut i = 0;
311 while i < bytes.len() {
312 hash ^= bytes[i] as u64;
313 hash = hash.wrapping_mul(FNV_PRIME);
314 i += 1;
315 }
316 hash
317}
318
319impl<T: Named + 'static> Key<T> {
320 pub const fn new(
322 name: &'static str,
323 default_value: Option<&'static T>,
324 attrs: &'static LazyLock<Attrs>,
325 ) -> Self {
326 Self {
327 name,
328 default_value,
329 attrs,
330 }
331 }
332
333 pub fn default(&self) -> Option<&'static T> {
335 self.default_value
336 }
337
338 pub fn has_default(&self) -> bool {
340 self.default_value.is_some()
341 }
342
343 pub fn typehash(&self) -> u64 {
345 T::typehash()
346 }
347
348 pub fn attrs(&self) -> &'static LazyLock<Attrs> {
350 self.attrs
351 }
352}
353
354impl<T: 'static> Clone for Key<T> {
355 fn clone(&self) -> Self {
356 *self
358 }
359}
360
361impl<T: 'static> Copy for Key<T> {}
362
363pub trait ErasedKey: Any + Send + Sync + 'static {
365 fn name(&self) -> &'static str;
367
368 fn key_hash(&self) -> u64;
370
371 fn typehash(&self) -> u64;
373
374 fn typename(&self) -> &'static str;
376}
377
378impl dyn ErasedKey {
379 pub fn downcast_ref<T: Named + 'static>(&'static self) -> Option<&'static Key<T>> {
381 (self as &dyn Any).downcast_ref::<Key<T>>()
382 }
383}
384
385impl<T: AttrValue> ErasedKey for Key<T> {
386 fn name(&self) -> &'static str {
387 self.name
388 }
389
390 fn key_hash(&self) -> u64 {
391 self.key_hash()
392 }
393
394 fn typehash(&self) -> u64 {
395 T::typehash()
396 }
397
398 fn typename(&self) -> &'static str {
399 T::typename()
400 }
401}
402
403impl<T: AttrValue> Index<Key<T>> for Attrs {
405 type Output = T;
406
407 fn index(&self, key: Key<T>) -> &Self::Output {
408 self.get(key).unwrap()
409 }
410}
411
412impl<T: AttrValue> IndexMut<Key<T>> for Attrs {
415 fn index_mut(&mut self, key: Key<T>) -> &mut Self::Output {
416 self.get_mut(key).unwrap()
417 }
418}
419
420pub trait AttrValue:
431 Named + Sized + Serialize + DeserializeOwned + Send + Sync + Clone + 'static
432{
433 fn display(&self) -> String;
436
437 fn parse(value: &str) -> Result<Self, anyhow::Error>;
439}
440
441trait DisplayNonEmpty {}
445
446#[macro_export]
460macro_rules! impl_attrvalue {
461 ($($ty:ty),+ $(,)?) => {
462 $(
463 impl $crate::attrs::AttrValue for $ty {
464 fn display(&self) -> String {
465 self.to_string()
466 }
467
468 fn parse(value: &str) -> Result<Self, anyhow::Error> {
469 value.parse().map_err(|e| anyhow::anyhow!("failed to parse {}: {}", stringify!($ty), e))
470 }
471 }
472 )+
473 };
474}
475
476#[macro_export]
477macro_rules! impl_attrvalue_and_display_non_empty {
478 ($($ty:ty),+ $(,)?) => {
479 $(
480 impl_attrvalue!($ty);
481 impl $crate::attrs::DisplayNonEmpty for $ty {}
482 )+
483 };
484}
485
486impl_attrvalue_and_display_non_empty!(
490 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64,
491);
492
493impl_attrvalue!(
496 String,
497 std::net::IpAddr,
498 std::net::Ipv4Addr,
499 std::net::Ipv6Addr,
500);
501
502impl AttrValue for std::time::Duration {
503 fn display(&self) -> String {
504 humantime::format_duration(*self).to_string()
505 }
506
507 fn parse(value: &str) -> Result<Self, anyhow::Error> {
508 Ok(humantime::parse_duration(value)?)
509 }
510}
511
512impl DisplayNonEmpty for std::time::Duration {}
514
515impl AttrValue for std::time::SystemTime {
516 fn display(&self) -> String {
517 let datetime: DateTime<Utc> = (*self).into();
518 datetime.to_rfc3339()
519 }
520
521 fn parse(value: &str) -> Result<Self, anyhow::Error> {
522 let datetime = DateTime::parse_from_rfc3339(value)?;
523 Ok(datetime.into())
524 }
525}
526
527impl DisplayNonEmpty for std::time::SystemTime {}
529
530impl<T> AttrValue for Vec<T>
531where
532 T: AttrValue,
533{
534 fn display(&self) -> String {
535 serde_json::to_string(self).unwrap_or_else(|_| "[]".to_string())
536 }
537
538 fn parse(value: &str) -> Result<Self, anyhow::Error> {
539 Ok(serde_json::from_str(value)?)
540 }
541}
542
543impl<T, E> AttrValue for std::ops::Range<T>
544where
545 T: Named
546 + Display
547 + FromStr<Err = E>
548 + Send
549 + Sync
550 + Serialize
551 + DeserializeOwned
552 + Clone
553 + 'static,
554 E: Into<anyhow::Error> + Send + Sync + 'static,
555{
556 fn display(&self) -> String {
557 format!("{}..{}", self.start, self.end)
558 }
559
560 fn parse(value: &str) -> Result<Self, anyhow::Error> {
561 let (start, end) = value.split_once("..").ok_or_else(|| {
562 anyhow::anyhow!("expected range in format `start..end`, got `{}`", value)
563 })?;
564 let start = start.parse().map_err(|e: E| e.into())?;
565 let end = end.parse().map_err(|e: E| e.into())?;
566 Ok(start..end)
567 }
568}
569
570impl AttrValue for bool {
571 fn display(&self) -> String {
572 if *self { 1.to_string() } else { 0.to_string() }
573 }
574
575 fn parse(value: &str) -> Result<Self, anyhow::Error> {
576 let value = value.to_ascii_lowercase();
577 match value.as_str() {
578 "0" | "false" => Ok(false),
579 "1" | "true" => Ok(true),
580 _ => Err(anyhow::anyhow!(
581 "expected `0`, `1`, `true` or `false`, got `{}`",
582 value
583 )),
584 }
585 }
586}
587
588impl DisplayNonEmpty for bool {}
589
590impl DisplayNonEmpty for crate::NonZeroUsize {}
591
592impl<T: AttrValue + DisplayNonEmpty> AttrValue for Option<T> {
593 fn display(&self) -> String {
594 match self {
595 Some(value) => value.display(),
596 None => String::new(),
597 }
598 }
599
600 fn parse(value: &str) -> Result<Self, anyhow::Error> {
601 if value.is_empty() {
602 Ok(None)
603 } else {
604 Ok(Some(T::parse(value)?))
605 }
606 }
607}
608
609#[doc(hidden)]
611pub trait SerializableValue: Send + Sync {
612 fn as_any(&self) -> &dyn Any;
614 fn as_any_mut(&mut self) -> &mut dyn Any;
616 fn as_erased_serialize(&self) -> &dyn ErasedSerialize;
618 fn cloned(&self) -> Box<dyn SerializableValue>;
620 fn display(&self) -> String;
622 fn serialize_bincode(&self) -> Vec<u8>;
624}
625
626impl<T: AttrValue> SerializableValue for T {
627 fn as_any(&self) -> &dyn Any {
628 self
629 }
630
631 fn as_any_mut(&mut self) -> &mut dyn Any {
632 self
633 }
634
635 fn as_erased_serialize(&self) -> &dyn ErasedSerialize {
636 self
637 }
638
639 fn cloned(&self) -> Box<dyn SerializableValue> {
640 Box::new(self.clone())
641 }
642
643 fn display(&self) -> String {
644 self.display()
645 }
646
647 fn serialize_bincode(&self) -> Vec<u8> {
648 bincode::serde::encode_to_vec(self, bincode::config::legacy())
649 .expect("bincode serialization failed")
650 }
651}
652
653pub struct Attrs {
671 values: HashMap<&'static str, Box<dyn SerializableValue>>,
672}
673
674impl Attrs {
675 pub fn new() -> Self {
677 Self {
678 values: HashMap::new(),
679 }
680 }
681
682 pub fn set<T: AttrValue>(&mut self, key: Key<T>, value: T) {
684 self.values.insert(key.name, Box::new(value));
685 }
686
687 fn maybe_set_from_default<T: AttrValue>(&mut self, key: Key<T>) {
688 if self.contains_key(key) {
689 return;
690 }
691 let Some(default) = key.default() else { return };
692 self.set(key, default.clone());
693 }
694
695 pub fn get<T: AttrValue>(&self, key: Key<T>) -> Option<&T> {
698 self.values
699 .get(key.name)
700 .and_then(|value| value.as_any().downcast_ref::<T>())
701 .or_else(|| key.default())
702 }
703
704 pub fn get_mut<T: AttrValue>(&mut self, key: Key<T>) -> Option<&mut T> {
707 self.maybe_set_from_default(key);
708 self.values
709 .get_mut(key.name)
710 .and_then(|value| value.as_any_mut().downcast_mut::<T>())
711 }
712
713 pub fn remove<T: AttrValue>(&mut self, key: Key<T>) -> bool {
715 self.values.remove(key.name).is_some()
717 }
718
719 pub fn contains_key<T: AttrValue>(&self, key: Key<T>) -> bool {
721 self.values.contains_key(key.name)
722 }
723
724 pub fn len(&self) -> usize {
726 self.values.len()
727 }
728
729 pub fn is_empty(&self) -> bool {
731 self.values.is_empty()
732 }
733
734 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &dyn SerializableValue)> {
738 self.values.iter().map(|(k, v)| (*k, v.as_ref()))
739 }
740
741 pub fn clear(&mut self) {
743 self.values.clear();
744 }
745
746 pub fn remove_value<T: 'static>(&mut self, key: Key<T>) -> Option<Box<dyn SerializableValue>> {
749 self.values.remove(key.name)
750 }
751
752 pub fn insert_value<T: 'static>(&mut self, key: Key<T>, value: Box<dyn SerializableValue>) {
754 self.values.insert(key.name, value);
755 }
756
757 pub fn insert_value_by_name_unchecked(
759 &mut self,
760 name: &'static str,
761 value: Box<dyn SerializableValue>,
762 ) {
763 self.values.insert(name, value);
764 }
765
766 pub fn get_value_by_name(&self, name: &'static str) -> Option<&dyn SerializableValue> {
769 self.values.get(name).map(|b| b.as_ref())
770 }
771
772 pub fn merge(&mut self, other: Attrs) {
778 self.values.extend(other.values);
779 }
780}
781
782impl Clone for Attrs {
783 fn clone(&self) -> Self {
784 let mut values = HashMap::new();
785 for (key, value) in &self.values {
786 values.insert(*key, value.cloned());
787 }
788 Self { values }
789 }
790}
791
792impl std::fmt::Display for Attrs {
793 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794 let mut first = true;
795 for (key, value) in &self.values {
796 if first {
797 first = false;
798 } else {
799 write!(f, ",")?;
800 }
801 write!(f, "{}={}", key, value.display())?
802 }
803 Ok(())
804 }
805}
806
807impl std::fmt::Debug for Attrs {
808 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
809 let mut debug_map = std::collections::BTreeMap::new();
811 for (key, value) in &self.values {
812 match serde_json::to_string(value.as_erased_serialize()) {
813 Ok(json) => {
814 debug_map.insert(*key, json);
815 }
816 Err(_) => {
817 debug_map.insert(*key, "<serialization error>".to_string());
818 }
819 }
820 }
821
822 f.debug_struct("Attrs").field("values", &debug_map).finish()
823 }
824}
825
826impl Default for Attrs {
827 fn default() -> Self {
828 Self::new()
829 }
830}
831
832impl Serialize for Attrs {
833 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
834 where
835 S: Serializer,
836 {
837 let mut map = serializer.serialize_map(Some(self.values.len()))?;
838
839 for (key_name, value) in &self.values {
840 map.serialize_entry(key_name, value.as_erased_serialize())?;
841 }
842
843 map.end()
844 }
845}
846
847struct AttrsVisitor;
848
849impl<'de> Visitor<'de> for AttrsVisitor {
850 type Value = Attrs;
851
852 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
853 formatter.write_str("a map of attribute keys to their serialized values")
854 }
855
856 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
857 where
858 M: MapAccess<'de>,
859 {
860 static KEYS_BY_NAME: std::sync::LazyLock<HashMap<&'static str, &'static AttrKeyInfo>> =
861 std::sync::LazyLock::new(|| {
862 inventory::iter::<AttrKeyInfo>()
863 .map(|info| (info.name, info))
864 .collect()
865 });
866 let keys_by_name = &*KEYS_BY_NAME;
867
868 let exe_name = std::env::current_exe()
869 .ok()
870 .map(|p| p.display().to_string())
871 .unwrap_or_else(|| "<unknown-exe>".to_string());
872
873 let mut attrs = Attrs::new();
874 while let Some(key_name) = access.next_key::<String>()? {
875 let Some(&key) = keys_by_name.get(key_name.as_str()) else {
876 access.next_value::<serde::de::IgnoredAny>().map_err(|_| {
896 serde::de::Error::custom(format!(
897 "unknown attr key '{}' on binary '{}'; \
898 this binary doesn't know this key and cannot skip its value safely under bincode",
899 key_name, exe_name,
900 ))
901 })?;
902 continue;
903 };
904
905 let seed = ValueDeserializeSeed {
907 deserialize_erased: key.deserialize_erased,
908 };
909 match access.next_value_seed(seed) {
910 Ok(value) => {
911 attrs.values.insert(key.name, value);
912 }
913 Err(err) => {
914 return Err(serde::de::Error::custom(format!(
915 "failed to deserialize value for key {}: {}",
916 key_name, err
917 )));
918 }
919 }
920 }
921
922 Ok(attrs)
923 }
924}
925
926struct ValueDeserializeSeed {
928 deserialize_erased:
929 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
930}
931
932impl<'de> serde::de::DeserializeSeed<'de> for ValueDeserializeSeed {
933 type Value = Box<dyn SerializableValue>;
934
935 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
936 where
937 D: serde::de::Deserializer<'de>,
938 {
939 let mut erased = <dyn erased_serde::Deserializer>::erase(deserializer);
940 (self.deserialize_erased)(&mut erased).map_err(serde::de::Error::custom)
941 }
942}
943
944impl<'de> Deserialize<'de> for Attrs {
945 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
946 where
947 D: Deserializer<'de>,
948 {
949 deserializer.deserialize_map(AttrsVisitor)
950 }
951}
952
953#[doc(hidden)]
956pub const fn ascii_to_lowercase_const<const N: usize>(input: &str) -> [u8; N] {
957 let bytes = input.as_bytes();
958 let mut result = [0u8; N];
959 let mut i = 0;
960
961 while i < bytes.len() && i < N {
962 let byte = bytes[i];
963 if byte >= b'A' && byte <= b'Z' {
964 result[i] = byte + 32; } else {
966 result[i] = byte;
967 }
968 i += 1;
969 }
970
971 result
972}
973
974#[doc(hidden)]
976#[macro_export]
977macro_rules! const_ascii_lowercase {
978 ($s:expr) => {{
979 const INPUT: &str = $s;
980 const LEN: usize = INPUT.len();
981 const BYTES: [u8; LEN] = $crate::attrs::ascii_to_lowercase_const::<LEN>(INPUT);
982 unsafe { std::str::from_utf8_unchecked(&BYTES) }
984 }};
985}
986
987#[doc(hidden)]
990#[macro_export]
991macro_rules! assert_impl {
992 ($ty:ty, $trait:path) => {
993 const _: fn() = || {
994 fn check<T: $trait>() {}
995 check::<$ty>();
996 };
997 };
998}
999
1000#[macro_export]
1043macro_rules! declare_attrs {
1044 ($(
1046 $(#[$attr:meta])*
1047 $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))*
1048 $vis:vis attr $name:ident: $type:ty $(= $default:expr)?;
1049 )*) => {
1050 $(
1051 $crate::declare_attrs! {
1052 @single
1053 $(@meta($($meta_key = $meta_value),*))*
1054 $(#[$attr])* ;
1055 $vis attr $name: $type $(= $default)?;
1056 }
1057 )*
1058 };
1059
1060 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty = $default:expr;) => {
1062 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1063
1064 $crate::paste! {
1066 static [<$name _DEFAULT>]: $type = $default;
1067 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
1068 std::sync::LazyLock::new(|| {
1069 #[allow(unused_mut)]
1070 let mut attrs = $crate::attrs::Attrs::new();
1071 $($(
1072 attrs.set($meta_key, $meta_value);
1073 )*)*
1074 attrs
1075 });
1076 }
1077
1078 $(#[$attr])*
1079 $vis static $name: $crate::attrs::Key<$type> = {
1080 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1081
1082 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1083 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1084 $crate::paste! {
1085 $crate::attrs::Key::new(
1086 LOWER_NAME,
1087 Some(&[<$name _DEFAULT>]),
1088 $crate::paste! { &[<$name _META_ATTRS>] },
1089 )
1090 }
1091 };
1092
1093 $crate::submit! {
1095 $crate::attrs::AttrKeyInfo {
1096 name: {
1097 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1098 $crate::const_ascii_lowercase!(FULL_NAME)
1099 },
1100 key_hash: {
1101 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1102 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1103 $crate::attrs::fnv1a_hash(LOWER_NAME.as_bytes())
1104 },
1105 typehash: <$type as $crate::typeuri::Named>::typehash,
1106 deserialize_erased: |deserializer| {
1107 let value: $type = erased_serde::deserialize(deserializer)?;
1108 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1109 },
1110 deserialize_bincode: |bytes| {
1111 let value: $type = $crate::bincode::serde::decode_from_slice(bytes, $crate::bincode::config::legacy()).map(|(v, _)| v)?;
1112 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1113 },
1114 meta: $crate::paste! { &[<$name _META_ATTRS>] },
1115 display: |value: &dyn $crate::attrs::SerializableValue| {
1116 let value = value.as_any().downcast_ref::<$type>().unwrap();
1117 $crate::attrs::AttrValue::display(value)
1118 },
1119 parse: |value: &str| {
1120 let value: $type = $crate::attrs::AttrValue::parse(value)?;
1121 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1122 },
1123 default: Some($crate::paste! { &[<$name _DEFAULT>] }),
1124 erased: &$name,
1125 }
1126 }
1127 };
1128
1129 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty;) => {
1131 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1132
1133 $crate::paste! {
1134 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
1135 std::sync::LazyLock::new(|| {
1136 #[allow(unused_mut)]
1137 let mut attrs = $crate::attrs::Attrs::new();
1138 $($(
1139 attrs.set($meta_key, $meta_value);
1142 )*)*
1143 attrs
1144 });
1145 }
1146
1147 $(#[$attr])*
1148 $vis static $name: $crate::attrs::Key<$type> = {
1149 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1150 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1151 $crate::attrs::Key::new(LOWER_NAME, None, $crate::paste! { &[<$name _META_ATTRS>] })
1152 };
1153
1154
1155 $crate::submit! {
1157 $crate::attrs::AttrKeyInfo {
1158 name: {
1159 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1160 $crate::const_ascii_lowercase!(FULL_NAME)
1161 },
1162 key_hash: {
1163 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1164 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1165 $crate::attrs::fnv1a_hash(LOWER_NAME.as_bytes())
1166 },
1167 typehash: <$type as $crate::typeuri::Named>::typehash,
1168 deserialize_erased: |deserializer| {
1169 let value: $type = erased_serde::deserialize(deserializer)?;
1170 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1171 },
1172 deserialize_bincode: |bytes| {
1173 let value: $type = $crate::bincode::serde::decode_from_slice(bytes, $crate::bincode::config::legacy()).map(|(v, _)| v)?;
1174 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1175 },
1176 meta: $crate::paste! { &[<$name _META_ATTRS>] },
1177 display: |value: &dyn $crate::attrs::SerializableValue| {
1178 let value = value.as_any().downcast_ref::<$type>().unwrap();
1179 $crate::attrs::AttrValue::display(value)
1180 },
1181 parse: |value: &str| {
1182 let value: $type = $crate::attrs::AttrValue::parse(value)?;
1183 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1184 },
1185 default: None,
1186 erased: &$name,
1187 }
1188 }
1189 };
1190}
1191
1192pub use declare_attrs;
1193
1194#[cfg(test)]
1195mod tests {
1196 use std::time::Duration;
1197
1198 use super::*;
1199
1200 declare_attrs! {
1201 attr TEST_TIMEOUT: Duration;
1202 attr TEST_COUNT: u32;
1203 @meta(TEST_COUNT = 42)
1204 pub attr TEST_NAME: String;
1205 attr TEST_OPTION_COUNT: Option<u32>;
1206 attr TEST_OPTION_TIMEOUT: Option<Duration>;
1207 }
1208
1209 #[test]
1210 fn test_basic_operations() {
1211 let mut attrs = Attrs::new();
1212
1213 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1215 attrs.set(TEST_COUNT, 42u32);
1216 attrs.set(TEST_NAME, "test".to_string());
1217
1218 assert_eq!(attrs.get(TEST_TIMEOUT), Some(&Duration::from_secs(5)));
1219 assert_eq!(attrs.get(TEST_COUNT), Some(&42u32));
1220 assert_eq!(attrs.get(TEST_NAME), Some(&"test".to_string()));
1221
1222 assert!(attrs.contains_key(TEST_TIMEOUT));
1224 assert!(attrs.contains_key(TEST_COUNT));
1225 assert!(attrs.contains_key(TEST_NAME));
1226
1227 assert_eq!(attrs.len(), 3);
1229 assert!(!attrs.is_empty());
1230
1231 assert_eq!(TEST_NAME.attrs().get(TEST_COUNT).unwrap(), &42u32);
1233 }
1234
1235 #[test]
1236 fn test_get_mut() {
1237 let mut attrs = Attrs::new();
1238 attrs.set(TEST_COUNT, 10u32);
1239
1240 if let Some(count) = attrs.get_mut(TEST_COUNT) {
1241 *count += 5;
1242 }
1243
1244 assert_eq!(attrs.get(TEST_COUNT), Some(&15u32));
1245 }
1246
1247 #[test]
1248 fn test_remove() {
1249 let mut attrs = Attrs::new();
1250 attrs.set(TEST_COUNT, 42u32);
1251
1252 let removed = attrs.remove(TEST_COUNT);
1253 assert!(removed);
1254 assert_eq!(attrs.get(TEST_COUNT), None);
1255 assert!(!attrs.contains_key(TEST_COUNT));
1256 }
1257
1258 #[test]
1259 fn test_clear() {
1260 let mut attrs = Attrs::new();
1261 attrs.set(TEST_TIMEOUT, Duration::from_secs(1));
1262 attrs.set(TEST_COUNT, 42u32);
1263
1264 attrs.clear();
1265 assert!(attrs.is_empty());
1266 assert_eq!(attrs.len(), 0);
1267 }
1268
1269 #[test]
1270 fn test_key_properties() {
1271 assert_eq!(
1272 TEST_TIMEOUT.name(),
1273 "hyperactor_config::attrs::tests::test_timeout"
1274 );
1275 }
1276
1277 #[test]
1278 fn test_serialization() {
1279 let mut attrs = Attrs::new();
1280 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1281 attrs.set(TEST_COUNT, 42u32);
1282 attrs.set(TEST_NAME, "test".to_string());
1283
1284 let serialized = serde_json::to_string(&attrs).expect("Failed to serialize");
1286
1287 assert!(serialized.contains("hyperactor_config::attrs::tests::test_timeout"));
1289 assert!(serialized.contains("hyperactor_config::attrs::tests::test_count"));
1290 assert!(serialized.contains("hyperactor_config::attrs::tests::test_name"));
1291 }
1292
1293 #[test]
1294 fn test_deserialization() {
1295 let mut original_attrs = Attrs::new();
1297 original_attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1298 original_attrs.set(TEST_COUNT, 42u32);
1299 original_attrs.set(TEST_NAME, "test".to_string());
1300
1301 let serialized = serde_json::to_string(&original_attrs).expect("Failed to serialize");
1303
1304 let deserialized_attrs: Attrs =
1306 serde_json::from_str(&serialized).expect("Failed to deserialize");
1307
1308 assert_eq!(
1310 deserialized_attrs.get(TEST_TIMEOUT),
1311 Some(&Duration::from_secs(5))
1312 );
1313 assert_eq!(deserialized_attrs.get(TEST_COUNT), Some(&42u32));
1314 assert_eq!(deserialized_attrs.get(TEST_NAME), Some(&"test".to_string()));
1315 }
1316
1317 #[test]
1318 fn test_roundtrip_serialization() {
1319 let mut original = Attrs::new();
1321 original.set(TEST_TIMEOUT, Duration::from_secs(10));
1322 original.set(TEST_COUNT, 5u32);
1323 original.set(TEST_OPTION_COUNT, Some(5u32));
1324 original.set(TEST_OPTION_TIMEOUT, None);
1325 original.set(TEST_NAME, "test-service".to_string());
1326
1327 let serialized = serde_json::to_string(&original).unwrap();
1329
1330 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1332
1333 assert_eq!(
1335 deserialized.get(TEST_TIMEOUT),
1336 Some(&Duration::from_secs(10))
1337 );
1338 assert_eq!(deserialized.get(TEST_COUNT), Some(&5u32));
1339 assert_eq!(deserialized.get(TEST_OPTION_COUNT), Some(&Some(5u32)));
1340 assert_eq!(deserialized.get(TEST_OPTION_TIMEOUT), Some(&None));
1341 assert_eq!(
1342 deserialized.get(TEST_NAME),
1343 Some(&"test-service".to_string())
1344 );
1345 }
1346
1347 #[test]
1348 fn test_empty_attrs_serialization() {
1349 let attrs = Attrs::new();
1350 let serialized = serde_json::to_string(&attrs).unwrap();
1351
1352 assert_eq!(serialized, "{}");
1354
1355 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1356
1357 assert!(deserialized.is_empty());
1358 }
1359
1360 #[test]
1361 fn test_format_independence() {
1362 let mut attrs = Attrs::new();
1364 attrs.set(TEST_COUNT, 42u32);
1365 attrs.set(TEST_NAME, "test".to_string());
1366
1367 let json_output = serde_json::to_string(&attrs).unwrap();
1369 let yaml_output = serde_yaml::to_string(&attrs).unwrap();
1370
1371 assert!(json_output.contains(":"));
1373 assert!(json_output.contains("\""));
1374
1375 assert!(json_output.contains("42"));
1377 assert!(!json_output.contains("\"42\""));
1378
1379 assert!(yaml_output.contains(":"));
1381 assert!(yaml_output.contains("42"));
1382
1383 assert!(!yaml_output.contains("\"42\""));
1385
1386 assert_ne!(json_output, yaml_output);
1388
1389 let from_json: Attrs = serde_json::from_str(&json_output).unwrap();
1391 let from_yaml: Attrs = serde_yaml::from_str(&yaml_output).unwrap();
1392
1393 assert_eq!(from_json.get(TEST_COUNT), Some(&42u32));
1394 assert_eq!(from_yaml.get(TEST_COUNT), Some(&42u32));
1395 assert_eq!(from_json.get(TEST_NAME), Some(&"test".to_string()));
1396 assert_eq!(from_yaml.get(TEST_NAME), Some(&"test".to_string()));
1397 }
1398
1399 #[test]
1400 fn test_clone() {
1401 let mut original = Attrs::new();
1403 original.set(TEST_COUNT, 42u32);
1404 original.set(TEST_NAME, "test".to_string());
1405 original.set(TEST_TIMEOUT, std::time::Duration::from_secs(10));
1406
1407 let cloned = original.clone();
1409
1410 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32));
1412 assert_eq!(cloned.get(TEST_NAME), Some(&"test".to_string()));
1413 assert_eq!(
1414 cloned.get(TEST_TIMEOUT),
1415 Some(&std::time::Duration::from_secs(10))
1416 );
1417
1418 original.set(TEST_COUNT, 100u32);
1420 assert_eq!(original.get(TEST_COUNT), Some(&100u32));
1421 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32)); let mut cloned_mut = cloned.clone();
1425 cloned_mut.set(TEST_NAME, "modified".to_string());
1426 assert_eq!(cloned_mut.get(TEST_NAME), Some(&"modified".to_string()));
1427 assert_eq!(original.get(TEST_NAME), Some(&"test".to_string())); }
1429
1430 #[test]
1431 fn test_debug_with_json() {
1432 let mut attrs = Attrs::new();
1433 attrs.set(TEST_COUNT, 42u32);
1434 attrs.set(TEST_NAME, "test".to_string());
1435
1436 let debug_output = format!("{:?}", attrs);
1438
1439 assert!(debug_output.contains("Attrs"));
1441
1442 assert!(debug_output.contains("42"));
1444
1445 assert!(debug_output.contains("hyperactor_config::attrs::tests::test_count"));
1447 assert!(debug_output.contains("hyperactor_config::attrs::tests::test_name"));
1448
1449 assert!(debug_output.contains("test"));
1452 }
1453
1454 declare_attrs! {
1455 attr TIMEOUT_WITH_DEFAULT: Duration = Duration::from_secs(10);
1457
1458 pub(crate) attr CRATE_LOCAL_ATTR: String;
1460 }
1461
1462 #[test]
1463 fn test_defaults() {
1464 assert!(TIMEOUT_WITH_DEFAULT.has_default());
1465 assert!(!CRATE_LOCAL_ATTR.has_default());
1466
1467 assert_eq!(
1468 Attrs::new().get(TIMEOUT_WITH_DEFAULT),
1469 Some(&Duration::from_secs(10))
1470 );
1471 }
1472
1473 #[test]
1474 fn test_indexing() {
1475 let mut attrs = Attrs::new();
1476
1477 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(10));
1478 attrs[TIMEOUT_WITH_DEFAULT] = Duration::from_secs(100);
1479 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(100));
1480
1481 attrs.set(CRATE_LOCAL_ATTR, "test".to_string());
1482 assert_eq!(attrs[CRATE_LOCAL_ATTR], "test".to_string());
1483 }
1484
1485 #[test]
1486 fn attrs_deserialize_unknown_key_is_error() {
1487 let bad_key: &'static str = "monarch_hyperactor::pytokio::unawaited_pytokio_traceback";
1512
1513 let mut attrs = Attrs::new();
1517 attrs.insert_value_by_name_unchecked(bad_key, Box::new(0u32));
1518
1519 let wire_bytes = bincode::serde::encode_to_vec(&attrs, bincode::config::legacy()).unwrap();
1522
1523 let err =
1526 bincode::serde::decode_from_slice::<Attrs, _>(&wire_bytes, bincode::config::legacy())
1527 .map(|(v, _)| v)
1528 .expect_err("should error on unknown attr key");
1529
1530 let exe_str = std::env::current_exe()
1531 .ok()
1532 .map(|p| p.display().to_string())
1533 .unwrap_or_else(|| "<unknown-exe>".to_string());
1534 let msg = format!("{err}");
1535
1536 assert!(msg.contains("unknown attr key"), "got: {msg}");
1537 assert!(msg.contains(bad_key), "got: {msg}");
1538 assert!(msg.contains(&exe_str), "got: {msg}");
1539 }
1540
1541 #[test]
1550 fn test_vec_string_attr() {
1551 declare_attrs! {
1552 attr TEST_VEC: Vec<String>;
1553 }
1554 let mut attrs = Attrs::new();
1555 let original = vec!["a".to_string(), "b".to_string()];
1556 attrs.set(TEST_VEC, original.clone());
1557 assert_eq!(attrs.get(TEST_VEC), Some(&original));
1558 let json = serde_json::to_string(&attrs).unwrap();
1560 let attrs2: Attrs = serde_json::from_str(&json).unwrap();
1561 assert_eq!(attrs2.get(TEST_VEC), Some(&original));
1562 let displayed = AttrValue::display(&original);
1564 let parsed: Vec<String> = AttrValue::parse(&displayed).unwrap();
1565 assert_eq!(parsed, original);
1566 }
1567
1568 declare_attrs! {
1569 pub attr TEST_GENERIC_MARKER: bool;
1574
1575 @meta(TEST_GENERIC_MARKER = true)
1577 pub attr TEST_MARKED_ATTR: String;
1578
1579 pub attr TEST_UNMARKED_ATTR: String;
1581 }
1582
1583 #[test]
1589 fn test_is_attr_marked_with() {
1590 assert_eq!(
1591 is_attr_marked_with(TEST_MARKED_ATTR.name(), TEST_GENERIC_MARKER),
1592 Some(true),
1593 "marked key must report Some(true)",
1594 );
1595 assert_eq!(
1596 is_attr_marked_with(TEST_UNMARKED_ATTR.name(), TEST_GENERIC_MARKER),
1597 None,
1598 "unmarked key must report None (marker not present on its meta)",
1599 );
1600 assert_eq!(
1601 is_attr_marked_with(
1602 "hyperactor_config::attrs::tests::no_such_key_declared_anywhere",
1603 TEST_GENERIC_MARKER,
1604 ),
1605 None,
1606 "unknown names must report None",
1607 );
1608 }
1609
1610 #[test]
1615 fn test_stamp_marked_attrs_into_flattrs() {
1616 use crate::flattrs::Flattrs;
1617
1618 let mut attrs = Attrs::new();
1619 attrs.set(TEST_MARKED_ATTR, "marked_value".to_string());
1620 attrs.set(TEST_UNMARKED_ATTR, "unmarked_value".to_string());
1621
1622 let mut headers = Flattrs::new();
1623 stamp_marked_attrs_into_flattrs(&mut headers, &attrs, TEST_GENERIC_MARKER);
1624
1625 assert_eq!(
1626 headers.get(TEST_MARKED_ATTR),
1627 Some("marked_value".to_string()),
1628 );
1629 assert_eq!(headers.get::<String>(TEST_UNMARKED_ATTR), None);
1630 }
1631
1632 #[test]
1638 fn test_copy_marked_flattrs() {
1639 use crate::flattrs::Flattrs;
1640
1641 let mut src = Flattrs::new();
1642 src.set(TEST_MARKED_ATTR, "marked".to_string());
1643 src.set(TEST_UNMARKED_ATTR, "unmarked".to_string());
1644
1645 let mut dst = Flattrs::new();
1646 copy_marked_flattrs(&mut dst, &src, TEST_GENERIC_MARKER);
1647
1648 assert_eq!(dst.get(TEST_MARKED_ATTR), Some("marked".to_string()),);
1649 assert_eq!(dst.get::<String>(TEST_UNMARKED_ATTR), None);
1650 }
1651
1652 #[test]
1656 fn test_marked_attr_names_enumeration() {
1657 let names = marked_attr_names(TEST_GENERIC_MARKER);
1658 assert!(
1659 names.contains(TEST_MARKED_ATTR.name()),
1660 "marked test attr must appear in the vocabulary enumeration",
1661 );
1662 assert!(
1663 !names.contains(TEST_UNMARKED_ATTR.name()),
1664 "unmarked test attr must not appear in the vocabulary enumeration",
1665 );
1666 }
1667}