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<T: AttrValue + DisplayNonEmpty> AttrValue for Option<T> {
591 fn display(&self) -> String {
592 match self {
593 Some(value) => value.display(),
594 None => String::new(),
595 }
596 }
597
598 fn parse(value: &str) -> Result<Self, anyhow::Error> {
599 if value.is_empty() {
600 Ok(None)
601 } else {
602 Ok(Some(T::parse(value)?))
603 }
604 }
605}
606
607#[doc(hidden)]
609pub trait SerializableValue: Send + Sync {
610 fn as_any(&self) -> &dyn Any;
612 fn as_any_mut(&mut self) -> &mut dyn Any;
614 fn as_erased_serialize(&self) -> &dyn ErasedSerialize;
616 fn cloned(&self) -> Box<dyn SerializableValue>;
618 fn display(&self) -> String;
620 fn serialize_bincode(&self) -> Vec<u8>;
622}
623
624impl<T: AttrValue> SerializableValue for T {
625 fn as_any(&self) -> &dyn Any {
626 self
627 }
628
629 fn as_any_mut(&mut self) -> &mut dyn Any {
630 self
631 }
632
633 fn as_erased_serialize(&self) -> &dyn ErasedSerialize {
634 self
635 }
636
637 fn cloned(&self) -> Box<dyn SerializableValue> {
638 Box::new(self.clone())
639 }
640
641 fn display(&self) -> String {
642 self.display()
643 }
644
645 fn serialize_bincode(&self) -> Vec<u8> {
646 bincode::serde::encode_to_vec(self, bincode::config::legacy())
647 .expect("bincode serialization failed")
648 }
649}
650
651pub struct Attrs {
669 values: HashMap<&'static str, Box<dyn SerializableValue>>,
670}
671
672impl Attrs {
673 pub fn new() -> Self {
675 Self {
676 values: HashMap::new(),
677 }
678 }
679
680 pub fn set<T: AttrValue>(&mut self, key: Key<T>, value: T) {
682 self.values.insert(key.name, Box::new(value));
683 }
684
685 fn maybe_set_from_default<T: AttrValue>(&mut self, key: Key<T>) {
686 if self.contains_key(key) {
687 return;
688 }
689 let Some(default) = key.default() else { return };
690 self.set(key, default.clone());
691 }
692
693 pub fn get<T: AttrValue>(&self, key: Key<T>) -> Option<&T> {
696 self.values
697 .get(key.name)
698 .and_then(|value| value.as_any().downcast_ref::<T>())
699 .or_else(|| key.default())
700 }
701
702 pub fn get_mut<T: AttrValue>(&mut self, key: Key<T>) -> Option<&mut T> {
705 self.maybe_set_from_default(key);
706 self.values
707 .get_mut(key.name)
708 .and_then(|value| value.as_any_mut().downcast_mut::<T>())
709 }
710
711 pub fn remove<T: AttrValue>(&mut self, key: Key<T>) -> bool {
713 self.values.remove(key.name).is_some()
715 }
716
717 pub fn contains_key<T: AttrValue>(&self, key: Key<T>) -> bool {
719 self.values.contains_key(key.name)
720 }
721
722 pub fn len(&self) -> usize {
724 self.values.len()
725 }
726
727 pub fn is_empty(&self) -> bool {
729 self.values.is_empty()
730 }
731
732 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &dyn SerializableValue)> {
736 self.values.iter().map(|(k, v)| (*k, v.as_ref()))
737 }
738
739 pub fn clear(&mut self) {
741 self.values.clear();
742 }
743
744 pub fn remove_value<T: 'static>(&mut self, key: Key<T>) -> Option<Box<dyn SerializableValue>> {
747 self.values.remove(key.name)
748 }
749
750 pub fn insert_value<T: 'static>(&mut self, key: Key<T>, value: Box<dyn SerializableValue>) {
752 self.values.insert(key.name, value);
753 }
754
755 pub fn insert_value_by_name_unchecked(
757 &mut self,
758 name: &'static str,
759 value: Box<dyn SerializableValue>,
760 ) {
761 self.values.insert(name, value);
762 }
763
764 pub fn get_value_by_name(&self, name: &'static str) -> Option<&dyn SerializableValue> {
767 self.values.get(name).map(|b| b.as_ref())
768 }
769
770 pub fn merge(&mut self, other: Attrs) {
776 self.values.extend(other.values);
777 }
778}
779
780impl Clone for Attrs {
781 fn clone(&self) -> Self {
782 let mut values = HashMap::new();
783 for (key, value) in &self.values {
784 values.insert(*key, value.cloned());
785 }
786 Self { values }
787 }
788}
789
790impl std::fmt::Display for Attrs {
791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792 let mut first = true;
793 for (key, value) in &self.values {
794 if first {
795 first = false;
796 } else {
797 write!(f, ",")?;
798 }
799 write!(f, "{}={}", key, value.display())?
800 }
801 Ok(())
802 }
803}
804
805impl std::fmt::Debug for Attrs {
806 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
807 let mut debug_map = std::collections::BTreeMap::new();
809 for (key, value) in &self.values {
810 match serde_json::to_string(value.as_erased_serialize()) {
811 Ok(json) => {
812 debug_map.insert(*key, json);
813 }
814 Err(_) => {
815 debug_map.insert(*key, "<serialization error>".to_string());
816 }
817 }
818 }
819
820 f.debug_struct("Attrs").field("values", &debug_map).finish()
821 }
822}
823
824impl Default for Attrs {
825 fn default() -> Self {
826 Self::new()
827 }
828}
829
830impl Serialize for Attrs {
831 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
832 where
833 S: Serializer,
834 {
835 let mut map = serializer.serialize_map(Some(self.values.len()))?;
836
837 for (key_name, value) in &self.values {
838 map.serialize_entry(key_name, value.as_erased_serialize())?;
839 }
840
841 map.end()
842 }
843}
844
845struct AttrsVisitor;
846
847impl<'de> Visitor<'de> for AttrsVisitor {
848 type Value = Attrs;
849
850 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
851 formatter.write_str("a map of attribute keys to their serialized values")
852 }
853
854 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
855 where
856 M: MapAccess<'de>,
857 {
858 static KEYS_BY_NAME: std::sync::LazyLock<HashMap<&'static str, &'static AttrKeyInfo>> =
859 std::sync::LazyLock::new(|| {
860 inventory::iter::<AttrKeyInfo>()
861 .map(|info| (info.name, info))
862 .collect()
863 });
864 let keys_by_name = &*KEYS_BY_NAME;
865
866 let exe_name = std::env::current_exe()
867 .ok()
868 .map(|p| p.display().to_string())
869 .unwrap_or_else(|| "<unknown-exe>".to_string());
870
871 let mut attrs = Attrs::new();
872 while let Some(key_name) = access.next_key::<String>()? {
873 let Some(&key) = keys_by_name.get(key_name.as_str()) else {
874 access.next_value::<serde::de::IgnoredAny>().map_err(|_| {
894 serde::de::Error::custom(format!(
895 "unknown attr key '{}' on binary '{}'; \
896 this binary doesn't know this key and cannot skip its value safely under bincode",
897 key_name, exe_name,
898 ))
899 })?;
900 continue;
901 };
902
903 let seed = ValueDeserializeSeed {
905 deserialize_erased: key.deserialize_erased,
906 };
907 match access.next_value_seed(seed) {
908 Ok(value) => {
909 attrs.values.insert(key.name, value);
910 }
911 Err(err) => {
912 return Err(serde::de::Error::custom(format!(
913 "failed to deserialize value for key {}: {}",
914 key_name, err
915 )));
916 }
917 }
918 }
919
920 Ok(attrs)
921 }
922}
923
924struct ValueDeserializeSeed {
926 deserialize_erased:
927 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
928}
929
930impl<'de> serde::de::DeserializeSeed<'de> for ValueDeserializeSeed {
931 type Value = Box<dyn SerializableValue>;
932
933 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
934 where
935 D: serde::de::Deserializer<'de>,
936 {
937 let mut erased = <dyn erased_serde::Deserializer>::erase(deserializer);
938 (self.deserialize_erased)(&mut erased).map_err(serde::de::Error::custom)
939 }
940}
941
942impl<'de> Deserialize<'de> for Attrs {
943 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
944 where
945 D: Deserializer<'de>,
946 {
947 deserializer.deserialize_map(AttrsVisitor)
948 }
949}
950
951#[doc(hidden)]
954pub const fn ascii_to_lowercase_const<const N: usize>(input: &str) -> [u8; N] {
955 let bytes = input.as_bytes();
956 let mut result = [0u8; N];
957 let mut i = 0;
958
959 while i < bytes.len() && i < N {
960 let byte = bytes[i];
961 if byte >= b'A' && byte <= b'Z' {
962 result[i] = byte + 32; } else {
964 result[i] = byte;
965 }
966 i += 1;
967 }
968
969 result
970}
971
972#[doc(hidden)]
974#[macro_export]
975macro_rules! const_ascii_lowercase {
976 ($s:expr) => {{
977 const INPUT: &str = $s;
978 const LEN: usize = INPUT.len();
979 const BYTES: [u8; LEN] = $crate::attrs::ascii_to_lowercase_const::<LEN>(INPUT);
980 unsafe { std::str::from_utf8_unchecked(&BYTES) }
982 }};
983}
984
985#[doc(hidden)]
988#[macro_export]
989macro_rules! assert_impl {
990 ($ty:ty, $trait:path) => {
991 const _: fn() = || {
992 fn check<T: $trait>() {}
993 check::<$ty>();
994 };
995 };
996}
997
998#[macro_export]
1041macro_rules! declare_attrs {
1042 ($(
1044 $(#[$attr:meta])*
1045 $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))*
1046 $vis:vis attr $name:ident: $type:ty $(= $default:expr)?;
1047 )*) => {
1048 $(
1049 $crate::declare_attrs! {
1050 @single
1051 $(@meta($($meta_key = $meta_value),*))*
1052 $(#[$attr])* ;
1053 $vis attr $name: $type $(= $default)?;
1054 }
1055 )*
1056 };
1057
1058 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty = $default:expr;) => {
1060 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1061
1062 $crate::paste! {
1064 static [<$name _DEFAULT>]: $type = $default;
1065 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
1066 std::sync::LazyLock::new(|| {
1067 #[allow(unused_mut)]
1068 let mut attrs = $crate::attrs::Attrs::new();
1069 $($(
1070 attrs.set($meta_key, $meta_value);
1071 )*)*
1072 attrs
1073 });
1074 }
1075
1076 $(#[$attr])*
1077 $vis static $name: $crate::attrs::Key<$type> = {
1078 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1079
1080 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1081 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1082 $crate::paste! {
1083 $crate::attrs::Key::new(
1084 LOWER_NAME,
1085 Some(&[<$name _DEFAULT>]),
1086 $crate::paste! { &[<$name _META_ATTRS>] },
1087 )
1088 }
1089 };
1090
1091 $crate::submit! {
1093 $crate::attrs::AttrKeyInfo {
1094 name: {
1095 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1096 $crate::const_ascii_lowercase!(FULL_NAME)
1097 },
1098 key_hash: {
1099 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1100 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1101 $crate::attrs::fnv1a_hash(LOWER_NAME.as_bytes())
1102 },
1103 typehash: <$type as $crate::typeuri::Named>::typehash,
1104 deserialize_erased: |deserializer| {
1105 let value: $type = erased_serde::deserialize(deserializer)?;
1106 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1107 },
1108 deserialize_bincode: |bytes| {
1109 let value: $type = $crate::bincode::serde::decode_from_slice(bytes, $crate::bincode::config::legacy()).map(|(v, _)| v)?;
1110 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1111 },
1112 meta: $crate::paste! { &[<$name _META_ATTRS>] },
1113 display: |value: &dyn $crate::attrs::SerializableValue| {
1114 let value = value.as_any().downcast_ref::<$type>().unwrap();
1115 $crate::attrs::AttrValue::display(value)
1116 },
1117 parse: |value: &str| {
1118 let value: $type = $crate::attrs::AttrValue::parse(value)?;
1119 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1120 },
1121 default: Some($crate::paste! { &[<$name _DEFAULT>] }),
1122 erased: &$name,
1123 }
1124 }
1125 };
1126
1127 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty;) => {
1129 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1130
1131 $crate::paste! {
1132 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
1133 std::sync::LazyLock::new(|| {
1134 #[allow(unused_mut)]
1135 let mut attrs = $crate::attrs::Attrs::new();
1136 $($(
1137 attrs.set($meta_key, $meta_value);
1140 )*)*
1141 attrs
1142 });
1143 }
1144
1145 $(#[$attr])*
1146 $vis static $name: $crate::attrs::Key<$type> = {
1147 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1148 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1149 $crate::attrs::Key::new(LOWER_NAME, None, $crate::paste! { &[<$name _META_ATTRS>] })
1150 };
1151
1152
1153 $crate::submit! {
1155 $crate::attrs::AttrKeyInfo {
1156 name: {
1157 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1158 $crate::const_ascii_lowercase!(FULL_NAME)
1159 },
1160 key_hash: {
1161 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1162 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1163 $crate::attrs::fnv1a_hash(LOWER_NAME.as_bytes())
1164 },
1165 typehash: <$type as $crate::typeuri::Named>::typehash,
1166 deserialize_erased: |deserializer| {
1167 let value: $type = erased_serde::deserialize(deserializer)?;
1168 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1169 },
1170 deserialize_bincode: |bytes| {
1171 let value: $type = $crate::bincode::serde::decode_from_slice(bytes, $crate::bincode::config::legacy()).map(|(v, _)| v)?;
1172 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1173 },
1174 meta: $crate::paste! { &[<$name _META_ATTRS>] },
1175 display: |value: &dyn $crate::attrs::SerializableValue| {
1176 let value = value.as_any().downcast_ref::<$type>().unwrap();
1177 $crate::attrs::AttrValue::display(value)
1178 },
1179 parse: |value: &str| {
1180 let value: $type = $crate::attrs::AttrValue::parse(value)?;
1181 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1182 },
1183 default: None,
1184 erased: &$name,
1185 }
1186 }
1187 };
1188}
1189
1190pub use declare_attrs;
1191
1192#[cfg(test)]
1193mod tests {
1194 use std::time::Duration;
1195
1196 use super::*;
1197
1198 declare_attrs! {
1199 attr TEST_TIMEOUT: Duration;
1200 attr TEST_COUNT: u32;
1201 @meta(TEST_COUNT = 42)
1202 pub attr TEST_NAME: String;
1203 attr TEST_OPTION_COUNT: Option<u32>;
1204 attr TEST_OPTION_TIMEOUT: Option<Duration>;
1205 }
1206
1207 #[test]
1208 fn test_basic_operations() {
1209 let mut attrs = Attrs::new();
1210
1211 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1213 attrs.set(TEST_COUNT, 42u32);
1214 attrs.set(TEST_NAME, "test".to_string());
1215
1216 assert_eq!(attrs.get(TEST_TIMEOUT), Some(&Duration::from_secs(5)));
1217 assert_eq!(attrs.get(TEST_COUNT), Some(&42u32));
1218 assert_eq!(attrs.get(TEST_NAME), Some(&"test".to_string()));
1219
1220 assert!(attrs.contains_key(TEST_TIMEOUT));
1222 assert!(attrs.contains_key(TEST_COUNT));
1223 assert!(attrs.contains_key(TEST_NAME));
1224
1225 assert_eq!(attrs.len(), 3);
1227 assert!(!attrs.is_empty());
1228
1229 assert_eq!(TEST_NAME.attrs().get(TEST_COUNT).unwrap(), &42u32);
1231 }
1232
1233 #[test]
1234 fn test_get_mut() {
1235 let mut attrs = Attrs::new();
1236 attrs.set(TEST_COUNT, 10u32);
1237
1238 if let Some(count) = attrs.get_mut(TEST_COUNT) {
1239 *count += 5;
1240 }
1241
1242 assert_eq!(attrs.get(TEST_COUNT), Some(&15u32));
1243 }
1244
1245 #[test]
1246 fn test_remove() {
1247 let mut attrs = Attrs::new();
1248 attrs.set(TEST_COUNT, 42u32);
1249
1250 let removed = attrs.remove(TEST_COUNT);
1251 assert!(removed);
1252 assert_eq!(attrs.get(TEST_COUNT), None);
1253 assert!(!attrs.contains_key(TEST_COUNT));
1254 }
1255
1256 #[test]
1257 fn test_clear() {
1258 let mut attrs = Attrs::new();
1259 attrs.set(TEST_TIMEOUT, Duration::from_secs(1));
1260 attrs.set(TEST_COUNT, 42u32);
1261
1262 attrs.clear();
1263 assert!(attrs.is_empty());
1264 assert_eq!(attrs.len(), 0);
1265 }
1266
1267 #[test]
1268 fn test_key_properties() {
1269 assert_eq!(
1270 TEST_TIMEOUT.name(),
1271 "hyperactor_config::attrs::tests::test_timeout"
1272 );
1273 }
1274
1275 #[test]
1276 fn test_serialization() {
1277 let mut attrs = Attrs::new();
1278 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1279 attrs.set(TEST_COUNT, 42u32);
1280 attrs.set(TEST_NAME, "test".to_string());
1281
1282 let serialized = serde_json::to_string(&attrs).expect("Failed to serialize");
1284
1285 assert!(serialized.contains("hyperactor_config::attrs::tests::test_timeout"));
1287 assert!(serialized.contains("hyperactor_config::attrs::tests::test_count"));
1288 assert!(serialized.contains("hyperactor_config::attrs::tests::test_name"));
1289 }
1290
1291 #[test]
1292 fn test_deserialization() {
1293 let mut original_attrs = Attrs::new();
1295 original_attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1296 original_attrs.set(TEST_COUNT, 42u32);
1297 original_attrs.set(TEST_NAME, "test".to_string());
1298
1299 let serialized = serde_json::to_string(&original_attrs).expect("Failed to serialize");
1301
1302 let deserialized_attrs: Attrs =
1304 serde_json::from_str(&serialized).expect("Failed to deserialize");
1305
1306 assert_eq!(
1308 deserialized_attrs.get(TEST_TIMEOUT),
1309 Some(&Duration::from_secs(5))
1310 );
1311 assert_eq!(deserialized_attrs.get(TEST_COUNT), Some(&42u32));
1312 assert_eq!(deserialized_attrs.get(TEST_NAME), Some(&"test".to_string()));
1313 }
1314
1315 #[test]
1316 fn test_roundtrip_serialization() {
1317 let mut original = Attrs::new();
1319 original.set(TEST_TIMEOUT, Duration::from_secs(10));
1320 original.set(TEST_COUNT, 5u32);
1321 original.set(TEST_OPTION_COUNT, Some(5u32));
1322 original.set(TEST_OPTION_TIMEOUT, None);
1323 original.set(TEST_NAME, "test-service".to_string());
1324
1325 let serialized = serde_json::to_string(&original).unwrap();
1327
1328 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1330
1331 assert_eq!(
1333 deserialized.get(TEST_TIMEOUT),
1334 Some(&Duration::from_secs(10))
1335 );
1336 assert_eq!(deserialized.get(TEST_COUNT), Some(&5u32));
1337 assert_eq!(deserialized.get(TEST_OPTION_COUNT), Some(&Some(5u32)));
1338 assert_eq!(deserialized.get(TEST_OPTION_TIMEOUT), Some(&None));
1339 assert_eq!(
1340 deserialized.get(TEST_NAME),
1341 Some(&"test-service".to_string())
1342 );
1343 }
1344
1345 #[test]
1346 fn test_empty_attrs_serialization() {
1347 let attrs = Attrs::new();
1348 let serialized = serde_json::to_string(&attrs).unwrap();
1349
1350 assert_eq!(serialized, "{}");
1352
1353 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1354
1355 assert!(deserialized.is_empty());
1356 }
1357
1358 #[test]
1359 fn test_format_independence() {
1360 let mut attrs = Attrs::new();
1362 attrs.set(TEST_COUNT, 42u32);
1363 attrs.set(TEST_NAME, "test".to_string());
1364
1365 let json_output = serde_json::to_string(&attrs).unwrap();
1367 let yaml_output = serde_yaml::to_string(&attrs).unwrap();
1368
1369 assert!(json_output.contains(":"));
1371 assert!(json_output.contains("\""));
1372
1373 assert!(json_output.contains("42"));
1375 assert!(!json_output.contains("\"42\""));
1376
1377 assert!(yaml_output.contains(":"));
1379 assert!(yaml_output.contains("42"));
1380
1381 assert!(!yaml_output.contains("\"42\""));
1383
1384 assert_ne!(json_output, yaml_output);
1386
1387 let from_json: Attrs = serde_json::from_str(&json_output).unwrap();
1389 let from_yaml: Attrs = serde_yaml::from_str(&yaml_output).unwrap();
1390
1391 assert_eq!(from_json.get(TEST_COUNT), Some(&42u32));
1392 assert_eq!(from_yaml.get(TEST_COUNT), Some(&42u32));
1393 assert_eq!(from_json.get(TEST_NAME), Some(&"test".to_string()));
1394 assert_eq!(from_yaml.get(TEST_NAME), Some(&"test".to_string()));
1395 }
1396
1397 #[test]
1398 fn test_clone() {
1399 let mut original = Attrs::new();
1401 original.set(TEST_COUNT, 42u32);
1402 original.set(TEST_NAME, "test".to_string());
1403 original.set(TEST_TIMEOUT, std::time::Duration::from_secs(10));
1404
1405 let cloned = original.clone();
1407
1408 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32));
1410 assert_eq!(cloned.get(TEST_NAME), Some(&"test".to_string()));
1411 assert_eq!(
1412 cloned.get(TEST_TIMEOUT),
1413 Some(&std::time::Duration::from_secs(10))
1414 );
1415
1416 original.set(TEST_COUNT, 100u32);
1418 assert_eq!(original.get(TEST_COUNT), Some(&100u32));
1419 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32)); let mut cloned_mut = cloned.clone();
1423 cloned_mut.set(TEST_NAME, "modified".to_string());
1424 assert_eq!(cloned_mut.get(TEST_NAME), Some(&"modified".to_string()));
1425 assert_eq!(original.get(TEST_NAME), Some(&"test".to_string())); }
1427
1428 #[test]
1429 fn test_debug_with_json() {
1430 let mut attrs = Attrs::new();
1431 attrs.set(TEST_COUNT, 42u32);
1432 attrs.set(TEST_NAME, "test".to_string());
1433
1434 let debug_output = format!("{:?}", attrs);
1436
1437 assert!(debug_output.contains("Attrs"));
1439
1440 assert!(debug_output.contains("42"));
1442
1443 assert!(debug_output.contains("hyperactor_config::attrs::tests::test_count"));
1445 assert!(debug_output.contains("hyperactor_config::attrs::tests::test_name"));
1446
1447 assert!(debug_output.contains("test"));
1450 }
1451
1452 declare_attrs! {
1453 attr TIMEOUT_WITH_DEFAULT: Duration = Duration::from_secs(10);
1455
1456 pub(crate) attr CRATE_LOCAL_ATTR: String;
1458 }
1459
1460 #[test]
1461 fn test_defaults() {
1462 assert!(TIMEOUT_WITH_DEFAULT.has_default());
1463 assert!(!CRATE_LOCAL_ATTR.has_default());
1464
1465 assert_eq!(
1466 Attrs::new().get(TIMEOUT_WITH_DEFAULT),
1467 Some(&Duration::from_secs(10))
1468 );
1469 }
1470
1471 #[test]
1472 fn test_indexing() {
1473 let mut attrs = Attrs::new();
1474
1475 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(10));
1476 attrs[TIMEOUT_WITH_DEFAULT] = Duration::from_secs(100);
1477 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(100));
1478
1479 attrs.set(CRATE_LOCAL_ATTR, "test".to_string());
1480 assert_eq!(attrs[CRATE_LOCAL_ATTR], "test".to_string());
1481 }
1482
1483 #[test]
1484 fn attrs_deserialize_unknown_key_is_error() {
1485 let bad_key: &'static str = "monarch_hyperactor::pytokio::unawaited_pytokio_traceback";
1510
1511 let mut attrs = Attrs::new();
1515 attrs.insert_value_by_name_unchecked(bad_key, Box::new(0u32));
1516
1517 let wire_bytes = bincode::serde::encode_to_vec(&attrs, bincode::config::legacy()).unwrap();
1520
1521 let err =
1524 bincode::serde::decode_from_slice::<Attrs, _>(&wire_bytes, bincode::config::legacy())
1525 .map(|(v, _)| v)
1526 .expect_err("should error on unknown attr key");
1527
1528 let exe_str = std::env::current_exe()
1529 .ok()
1530 .map(|p| p.display().to_string())
1531 .unwrap_or_else(|| "<unknown-exe>".to_string());
1532 let msg = format!("{err}");
1533
1534 assert!(msg.contains("unknown attr key"), "got: {msg}");
1535 assert!(msg.contains(bad_key), "got: {msg}");
1536 assert!(msg.contains(&exe_str), "got: {msg}");
1537 }
1538
1539 #[test]
1548 fn test_vec_string_attr() {
1549 declare_attrs! {
1550 attr TEST_VEC: Vec<String>;
1551 }
1552 let mut attrs = Attrs::new();
1553 let original = vec!["a".to_string(), "b".to_string()];
1554 attrs.set(TEST_VEC, original.clone());
1555 assert_eq!(attrs.get(TEST_VEC), Some(&original));
1556 let json = serde_json::to_string(&attrs).unwrap();
1558 let attrs2: Attrs = serde_json::from_str(&json).unwrap();
1559 assert_eq!(attrs2.get(TEST_VEC), Some(&original));
1560 let displayed = AttrValue::display(&original);
1562 let parsed: Vec<String> = AttrValue::parse(&displayed).unwrap();
1563 assert_eq!(parsed, original);
1564 }
1565
1566 declare_attrs! {
1567 pub attr TEST_GENERIC_MARKER: bool;
1572
1573 @meta(TEST_GENERIC_MARKER = true)
1575 pub attr TEST_MARKED_ATTR: String;
1576
1577 pub attr TEST_UNMARKED_ATTR: String;
1579 }
1580
1581 #[test]
1587 fn test_is_attr_marked_with() {
1588 assert_eq!(
1589 is_attr_marked_with(TEST_MARKED_ATTR.name(), TEST_GENERIC_MARKER),
1590 Some(true),
1591 "marked key must report Some(true)",
1592 );
1593 assert_eq!(
1594 is_attr_marked_with(TEST_UNMARKED_ATTR.name(), TEST_GENERIC_MARKER),
1595 None,
1596 "unmarked key must report None (marker not present on its meta)",
1597 );
1598 assert_eq!(
1599 is_attr_marked_with(
1600 "hyperactor_config::attrs::tests::no_such_key_declared_anywhere",
1601 TEST_GENERIC_MARKER,
1602 ),
1603 None,
1604 "unknown names must report None",
1605 );
1606 }
1607
1608 #[test]
1613 fn test_stamp_marked_attrs_into_flattrs() {
1614 use crate::flattrs::Flattrs;
1615
1616 let mut attrs = Attrs::new();
1617 attrs.set(TEST_MARKED_ATTR, "marked_value".to_string());
1618 attrs.set(TEST_UNMARKED_ATTR, "unmarked_value".to_string());
1619
1620 let mut headers = Flattrs::new();
1621 stamp_marked_attrs_into_flattrs(&mut headers, &attrs, TEST_GENERIC_MARKER);
1622
1623 assert_eq!(
1624 headers.get(TEST_MARKED_ATTR),
1625 Some("marked_value".to_string()),
1626 );
1627 assert_eq!(headers.get::<String>(TEST_UNMARKED_ATTR), None);
1628 }
1629
1630 #[test]
1636 fn test_copy_marked_flattrs() {
1637 use crate::flattrs::Flattrs;
1638
1639 let mut src = Flattrs::new();
1640 src.set(TEST_MARKED_ATTR, "marked".to_string());
1641 src.set(TEST_UNMARKED_ATTR, "unmarked".to_string());
1642
1643 let mut dst = Flattrs::new();
1644 copy_marked_flattrs(&mut dst, &src, TEST_GENERIC_MARKER);
1645
1646 assert_eq!(dst.get(TEST_MARKED_ATTR), Some("marked".to_string()),);
1647 assert_eq!(dst.get::<String>(TEST_UNMARKED_ATTR), None);
1648 }
1649
1650 #[test]
1654 fn test_marked_attr_names_enumeration() {
1655 let names = marked_attr_names(TEST_GENERIC_MARKER);
1656 assert!(
1657 names.contains(TEST_MARKED_ATTR.name()),
1658 "marked test attr must appear in the vocabulary enumeration",
1659 );
1660 assert!(
1661 !names.contains(TEST_UNMARKED_ATTR.name()),
1662 "unmarked test attr must not appear in the vocabulary enumeration",
1663 );
1664 }
1665}