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
119#[doc(hidden)]
123pub struct AttrKeyInfo {
124 pub name: &'static str,
126 pub key_hash: u64,
128 pub typehash: fn() -> u64,
130 pub deserialize_erased:
132 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
133 pub deserialize_bincode:
135 fn(&[u8]) -> Result<Box<dyn SerializableValue>, Box<bincode::ErrorKind>>,
136 pub meta: &'static LazyLock<Attrs>,
138 pub display: fn(&dyn SerializableValue) -> String,
140 pub parse: fn(&str) -> Result<Box<dyn SerializableValue>, anyhow::Error>,
142 pub default: Option<&'static dyn SerializableValue>,
144 pub erased: &'static dyn ErasedKey,
147}
148
149inventory::collect!(AttrKeyInfo);
150
151pub fn lookup_key_info(key_hash: u64) -> Option<&'static AttrKeyInfo> {
156 static KEYS_BY_HASH: std::sync::LazyLock<std::collections::HashMap<u64, &'static AttrKeyInfo>> =
157 std::sync::LazyLock::new(|| {
158 inventory::iter::<AttrKeyInfo>()
159 .map(|info| (info.key_hash, info))
160 .collect()
161 });
162 KEYS_BY_HASH.get(&key_hash).copied()
163}
164
165pub struct Key<T: 'static> {
171 name: &'static str,
172 default_value: Option<&'static T>,
173 attrs: &'static LazyLock<Attrs>,
174}
175
176impl<T> Key<T> {
177 pub fn name(&self) -> &'static str {
179 self.name
180 }
181
182 pub const fn key_hash(&self) -> u64 {
187 fnv1a_hash(self.name.as_bytes())
188 }
189}
190
191pub const fn fnv1a_hash(bytes: &[u8]) -> u64 {
196 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
197 const FNV_PRIME: u64 = 0x100000001b3;
198
199 let mut hash = FNV_OFFSET_BASIS;
200 let mut i = 0;
201 while i < bytes.len() {
202 hash ^= bytes[i] as u64;
203 hash = hash.wrapping_mul(FNV_PRIME);
204 i += 1;
205 }
206 hash
207}
208
209impl<T: Named + 'static> Key<T> {
210 pub const fn new(
212 name: &'static str,
213 default_value: Option<&'static T>,
214 attrs: &'static LazyLock<Attrs>,
215 ) -> Self {
216 Self {
217 name,
218 default_value,
219 attrs,
220 }
221 }
222
223 pub fn default(&self) -> Option<&'static T> {
225 self.default_value
226 }
227
228 pub fn has_default(&self) -> bool {
230 self.default_value.is_some()
231 }
232
233 pub fn typehash(&self) -> u64 {
235 T::typehash()
236 }
237
238 pub fn attrs(&self) -> &'static LazyLock<Attrs> {
240 self.attrs
241 }
242}
243
244impl<T: 'static> Clone for Key<T> {
245 fn clone(&self) -> Self {
246 *self
248 }
249}
250
251impl<T: 'static> Copy for Key<T> {}
252
253pub trait ErasedKey: Any + Send + Sync + 'static {
255 fn name(&self) -> &'static str;
257
258 fn key_hash(&self) -> u64;
260
261 fn typehash(&self) -> u64;
263
264 fn typename(&self) -> &'static str;
266}
267
268impl dyn ErasedKey {
269 pub fn downcast_ref<T: Named + 'static>(&'static self) -> Option<&'static Key<T>> {
271 (self as &dyn Any).downcast_ref::<Key<T>>()
272 }
273}
274
275impl<T: AttrValue> ErasedKey for Key<T> {
276 fn name(&self) -> &'static str {
277 self.name
278 }
279
280 fn key_hash(&self) -> u64 {
281 self.key_hash()
282 }
283
284 fn typehash(&self) -> u64 {
285 T::typehash()
286 }
287
288 fn typename(&self) -> &'static str {
289 T::typename()
290 }
291}
292
293impl<T: AttrValue> Index<Key<T>> for Attrs {
295 type Output = T;
296
297 fn index(&self, key: Key<T>) -> &Self::Output {
298 self.get(key).unwrap()
299 }
300}
301
302impl<T: AttrValue> IndexMut<Key<T>> for Attrs {
305 fn index_mut(&mut self, key: Key<T>) -> &mut Self::Output {
306 self.get_mut(key).unwrap()
307 }
308}
309
310pub trait AttrValue:
321 Named + Sized + Serialize + DeserializeOwned + Send + Sync + Clone + 'static
322{
323 fn display(&self) -> String;
326
327 fn parse(value: &str) -> Result<Self, anyhow::Error>;
329}
330
331trait DisplayNonEmpty {}
335
336#[macro_export]
350macro_rules! impl_attrvalue {
351 ($($ty:ty),+ $(,)?) => {
352 $(
353 impl $crate::attrs::AttrValue for $ty {
354 fn display(&self) -> String {
355 self.to_string()
356 }
357
358 fn parse(value: &str) -> Result<Self, anyhow::Error> {
359 value.parse().map_err(|e| anyhow::anyhow!("failed to parse {}: {}", stringify!($ty), e))
360 }
361 }
362 )+
363 };
364}
365
366#[macro_export]
367macro_rules! impl_attrvalue_and_display_non_empty {
368 ($($ty:ty),+ $(,)?) => {
369 $(
370 impl_attrvalue!($ty);
371 impl $crate::attrs::DisplayNonEmpty for $ty {}
372 )+
373 };
374}
375
376impl_attrvalue_and_display_non_empty!(
380 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64,
381);
382
383impl_attrvalue!(
386 String,
387 std::net::IpAddr,
388 std::net::Ipv4Addr,
389 std::net::Ipv6Addr,
390);
391
392impl AttrValue for std::time::Duration {
393 fn display(&self) -> String {
394 humantime::format_duration(*self).to_string()
395 }
396
397 fn parse(value: &str) -> Result<Self, anyhow::Error> {
398 Ok(humantime::parse_duration(value)?)
399 }
400}
401
402impl DisplayNonEmpty for std::time::Duration {}
404
405impl AttrValue for std::time::SystemTime {
406 fn display(&self) -> String {
407 let datetime: DateTime<Utc> = (*self).into();
408 datetime.to_rfc3339()
409 }
410
411 fn parse(value: &str) -> Result<Self, anyhow::Error> {
412 let datetime = DateTime::parse_from_rfc3339(value)?;
413 Ok(datetime.into())
414 }
415}
416
417impl DisplayNonEmpty for std::time::SystemTime {}
419
420impl<T> AttrValue for Vec<T>
421where
422 T: AttrValue,
423{
424 fn display(&self) -> String {
425 serde_json::to_string(self).unwrap_or_else(|_| "[]".to_string())
426 }
427
428 fn parse(value: &str) -> Result<Self, anyhow::Error> {
429 Ok(serde_json::from_str(value)?)
430 }
431}
432
433impl<T, E> AttrValue for std::ops::Range<T>
434where
435 T: Named
436 + Display
437 + FromStr<Err = E>
438 + Send
439 + Sync
440 + Serialize
441 + DeserializeOwned
442 + Clone
443 + 'static,
444 E: Into<anyhow::Error> + Send + Sync + 'static,
445{
446 fn display(&self) -> String {
447 format!("{}..{}", self.start, self.end)
448 }
449
450 fn parse(value: &str) -> Result<Self, anyhow::Error> {
451 let (start, end) = value.split_once("..").ok_or_else(|| {
452 anyhow::anyhow!("expected range in format `start..end`, got `{}`", value)
453 })?;
454 let start = start.parse().map_err(|e: E| e.into())?;
455 let end = end.parse().map_err(|e: E| e.into())?;
456 Ok(start..end)
457 }
458}
459
460impl AttrValue for bool {
461 fn display(&self) -> String {
462 if *self { 1.to_string() } else { 0.to_string() }
463 }
464
465 fn parse(value: &str) -> Result<Self, anyhow::Error> {
466 let value = value.to_ascii_lowercase();
467 match value.as_str() {
468 "0" | "false" => Ok(false),
469 "1" | "true" => Ok(true),
470 _ => Err(anyhow::anyhow!(
471 "expected `0`, `1`, `true` or `false`, got `{}`",
472 value
473 )),
474 }
475 }
476}
477
478impl DisplayNonEmpty for bool {}
479
480impl<T: AttrValue + DisplayNonEmpty> AttrValue for Option<T> {
481 fn display(&self) -> String {
482 match self {
483 Some(value) => value.display(),
484 None => String::new(),
485 }
486 }
487
488 fn parse(value: &str) -> Result<Self, anyhow::Error> {
489 if value.is_empty() {
490 Ok(None)
491 } else {
492 Ok(Some(T::parse(value)?))
493 }
494 }
495}
496
497#[doc(hidden)]
499pub trait SerializableValue: Send + Sync {
500 fn as_any(&self) -> &dyn Any;
502 fn as_any_mut(&mut self) -> &mut dyn Any;
504 fn as_erased_serialize(&self) -> &dyn ErasedSerialize;
506 fn cloned(&self) -> Box<dyn SerializableValue>;
508 fn display(&self) -> String;
510 fn serialize_bincode(&self) -> Vec<u8>;
512}
513
514impl<T: AttrValue> SerializableValue for T {
515 fn as_any(&self) -> &dyn Any {
516 self
517 }
518
519 fn as_any_mut(&mut self) -> &mut dyn Any {
520 self
521 }
522
523 fn as_erased_serialize(&self) -> &dyn ErasedSerialize {
524 self
525 }
526
527 fn cloned(&self) -> Box<dyn SerializableValue> {
528 Box::new(self.clone())
529 }
530
531 fn display(&self) -> String {
532 self.display()
533 }
534
535 fn serialize_bincode(&self) -> Vec<u8> {
536 bincode::serialize(self).expect("bincode serialization failed")
537 }
538}
539
540pub struct Attrs {
558 values: HashMap<&'static str, Box<dyn SerializableValue>>,
559}
560
561impl Attrs {
562 pub fn new() -> Self {
564 Self {
565 values: HashMap::new(),
566 }
567 }
568
569 pub fn set<T: AttrValue>(&mut self, key: Key<T>, value: T) {
571 self.values.insert(key.name, Box::new(value));
572 }
573
574 fn maybe_set_from_default<T: AttrValue>(&mut self, key: Key<T>) {
575 if self.contains_key(key) {
576 return;
577 }
578 let Some(default) = key.default() else { return };
579 self.set(key, default.clone());
580 }
581
582 pub fn get<T: AttrValue>(&self, key: Key<T>) -> Option<&T> {
585 self.values
586 .get(key.name)
587 .and_then(|value| value.as_any().downcast_ref::<T>())
588 .or_else(|| key.default())
589 }
590
591 pub fn get_mut<T: AttrValue>(&mut self, key: Key<T>) -> Option<&mut T> {
594 self.maybe_set_from_default(key);
595 self.values
596 .get_mut(key.name)
597 .and_then(|value| value.as_any_mut().downcast_mut::<T>())
598 }
599
600 pub fn remove<T: AttrValue>(&mut self, key: Key<T>) -> bool {
602 self.values.remove(key.name).is_some()
604 }
605
606 pub fn contains_key<T: AttrValue>(&self, key: Key<T>) -> bool {
608 self.values.contains_key(key.name)
609 }
610
611 pub fn len(&self) -> usize {
613 self.values.len()
614 }
615
616 pub fn is_empty(&self) -> bool {
618 self.values.is_empty()
619 }
620
621 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &dyn SerializableValue)> {
625 self.values.iter().map(|(k, v)| (*k, v.as_ref()))
626 }
627
628 pub fn clear(&mut self) {
630 self.values.clear();
631 }
632
633 pub fn remove_value<T: 'static>(&mut self, key: Key<T>) -> Option<Box<dyn SerializableValue>> {
636 self.values.remove(key.name)
637 }
638
639 pub fn insert_value<T: 'static>(&mut self, key: Key<T>, value: Box<dyn SerializableValue>) {
641 self.values.insert(key.name, value);
642 }
643
644 pub fn insert_value_by_name_unchecked(
646 &mut self,
647 name: &'static str,
648 value: Box<dyn SerializableValue>,
649 ) {
650 self.values.insert(name, value);
651 }
652
653 pub fn get_value_by_name(&self, name: &'static str) -> Option<&dyn SerializableValue> {
656 self.values.get(name).map(|b| b.as_ref())
657 }
658
659 pub fn merge(&mut self, other: Attrs) {
665 self.values.extend(other.values);
666 }
667}
668
669impl Clone for Attrs {
670 fn clone(&self) -> Self {
671 let mut values = HashMap::new();
672 for (key, value) in &self.values {
673 values.insert(*key, value.cloned());
674 }
675 Self { values }
676 }
677}
678
679impl std::fmt::Display for Attrs {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 let mut first = true;
682 for (key, value) in &self.values {
683 if first {
684 first = false;
685 } else {
686 write!(f, ",")?;
687 }
688 write!(f, "{}={}", key, value.display())?
689 }
690 Ok(())
691 }
692}
693
694impl std::fmt::Debug for Attrs {
695 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
696 let mut debug_map = std::collections::BTreeMap::new();
698 for (key, value) in &self.values {
699 match serde_json::to_string(value.as_erased_serialize()) {
700 Ok(json) => {
701 debug_map.insert(*key, json);
702 }
703 Err(_) => {
704 debug_map.insert(*key, "<serialization error>".to_string());
705 }
706 }
707 }
708
709 f.debug_struct("Attrs").field("values", &debug_map).finish()
710 }
711}
712
713impl Default for Attrs {
714 fn default() -> Self {
715 Self::new()
716 }
717}
718
719impl Serialize for Attrs {
720 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
721 where
722 S: Serializer,
723 {
724 let mut map = serializer.serialize_map(Some(self.values.len()))?;
725
726 for (key_name, value) in &self.values {
727 map.serialize_entry(key_name, value.as_erased_serialize())?;
728 }
729
730 map.end()
731 }
732}
733
734struct AttrsVisitor;
735
736impl<'de> Visitor<'de> for AttrsVisitor {
737 type Value = Attrs;
738
739 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
740 formatter.write_str("a map of attribute keys to their serialized values")
741 }
742
743 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
744 where
745 M: MapAccess<'de>,
746 {
747 static KEYS_BY_NAME: std::sync::LazyLock<HashMap<&'static str, &'static AttrKeyInfo>> =
748 std::sync::LazyLock::new(|| {
749 inventory::iter::<AttrKeyInfo>()
750 .map(|info| (info.name, info))
751 .collect()
752 });
753 let keys_by_name = &*KEYS_BY_NAME;
754
755 let exe_name = std::env::current_exe()
756 .ok()
757 .map(|p| p.display().to_string())
758 .unwrap_or_else(|| "<unknown-exe>".to_string());
759
760 let mut attrs = Attrs::new();
761 while let Some(key_name) = access.next_key::<String>()? {
762 let Some(&key) = keys_by_name.get(key_name.as_str()) else {
763 access.next_value::<serde::de::IgnoredAny>().map_err(|_| {
783 serde::de::Error::custom(format!(
784 "unknown attr key '{}' on binary '{}'; \
785 this binary doesn't know this key and cannot skip its value safely under bincode",
786 key_name, exe_name,
787 ))
788 })?;
789 continue;
790 };
791
792 let seed = ValueDeserializeSeed {
794 deserialize_erased: key.deserialize_erased,
795 };
796 match access.next_value_seed(seed) {
797 Ok(value) => {
798 attrs.values.insert(key.name, value);
799 }
800 Err(err) => {
801 return Err(serde::de::Error::custom(format!(
802 "failed to deserialize value for key {}: {}",
803 key_name, err
804 )));
805 }
806 }
807 }
808
809 Ok(attrs)
810 }
811}
812
813struct ValueDeserializeSeed {
815 deserialize_erased:
816 fn(&mut dyn ErasedDeserializer) -> Result<Box<dyn SerializableValue>, erased_serde::Error>,
817}
818
819impl<'de> serde::de::DeserializeSeed<'de> for ValueDeserializeSeed {
820 type Value = Box<dyn SerializableValue>;
821
822 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
823 where
824 D: serde::de::Deserializer<'de>,
825 {
826 let mut erased = <dyn erased_serde::Deserializer>::erase(deserializer);
827 (self.deserialize_erased)(&mut erased).map_err(serde::de::Error::custom)
828 }
829}
830
831impl<'de> Deserialize<'de> for Attrs {
832 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
833 where
834 D: Deserializer<'de>,
835 {
836 deserializer.deserialize_map(AttrsVisitor)
837 }
838}
839
840#[doc(hidden)]
843pub const fn ascii_to_lowercase_const<const N: usize>(input: &str) -> [u8; N] {
844 let bytes = input.as_bytes();
845 let mut result = [0u8; N];
846 let mut i = 0;
847
848 while i < bytes.len() && i < N {
849 let byte = bytes[i];
850 if byte >= b'A' && byte <= b'Z' {
851 result[i] = byte + 32; } else {
853 result[i] = byte;
854 }
855 i += 1;
856 }
857
858 result
859}
860
861#[doc(hidden)]
863#[macro_export]
864macro_rules! const_ascii_lowercase {
865 ($s:expr) => {{
866 const INPUT: &str = $s;
867 const LEN: usize = INPUT.len();
868 const BYTES: [u8; LEN] = $crate::attrs::ascii_to_lowercase_const::<LEN>(INPUT);
869 unsafe { std::str::from_utf8_unchecked(&BYTES) }
871 }};
872}
873
874#[doc(hidden)]
877#[macro_export]
878macro_rules! assert_impl {
879 ($ty:ty, $trait:path) => {
880 const _: fn() = || {
881 fn check<T: $trait>() {}
882 check::<$ty>();
883 };
884 };
885}
886
887#[macro_export]
930macro_rules! declare_attrs {
931 ($(
933 $(#[$attr:meta])*
934 $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))*
935 $vis:vis attr $name:ident: $type:ty $(= $default:expr)?;
936 )*) => {
937 $(
938 $crate::declare_attrs! {
939 @single
940 $(@meta($($meta_key = $meta_value),*))*
941 $(#[$attr])* ;
942 $vis attr $name: $type $(= $default)?;
943 }
944 )*
945 };
946
947 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty = $default:expr;) => {
949 $crate::assert_impl!($type, $crate::attrs::AttrValue);
950
951 $crate::paste! {
953 static [<$name _DEFAULT>]: $type = $default;
954 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
955 std::sync::LazyLock::new(|| {
956 #[allow(unused_mut)]
957 let mut attrs = $crate::attrs::Attrs::new();
958 $($(
959 attrs.set($meta_key, $meta_value);
960 )*)*
961 attrs
962 });
963 }
964
965 $(#[$attr])*
966 $vis static $name: $crate::attrs::Key<$type> = {
967 $crate::assert_impl!($type, $crate::attrs::AttrValue);
968
969 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
970 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
971 $crate::paste! {
972 $crate::attrs::Key::new(
973 LOWER_NAME,
974 Some(&[<$name _DEFAULT>]),
975 $crate::paste! { &[<$name _META_ATTRS>] },
976 )
977 }
978 };
979
980 $crate::submit! {
982 $crate::attrs::AttrKeyInfo {
983 name: {
984 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
985 $crate::const_ascii_lowercase!(FULL_NAME)
986 },
987 key_hash: {
988 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
989 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
990 $crate::attrs::fnv1a_hash(LOWER_NAME.as_bytes())
991 },
992 typehash: <$type as $crate::typeuri::Named>::typehash,
993 deserialize_erased: |deserializer| {
994 let value: $type = erased_serde::deserialize(deserializer)?;
995 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
996 },
997 deserialize_bincode: |bytes| {
998 let value: $type = $crate::bincode::deserialize(bytes)?;
999 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1000 },
1001 meta: $crate::paste! { &[<$name _META_ATTRS>] },
1002 display: |value: &dyn $crate::attrs::SerializableValue| {
1003 let value = value.as_any().downcast_ref::<$type>().unwrap();
1004 $crate::attrs::AttrValue::display(value)
1005 },
1006 parse: |value: &str| {
1007 let value: $type = $crate::attrs::AttrValue::parse(value)?;
1008 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1009 },
1010 default: Some($crate::paste! { &[<$name _DEFAULT>] }),
1011 erased: &$name,
1012 }
1013 }
1014 };
1015
1016 (@single $(@meta($($meta_key:ident = $meta_value:expr),* $(,)?))* $(#[$attr:meta])* ; $vis:vis attr $name:ident: $type:ty;) => {
1018 $crate::assert_impl!($type, $crate::attrs::AttrValue);
1019
1020 $crate::paste! {
1021 static [<$name _META_ATTRS>]: std::sync::LazyLock<$crate::attrs::Attrs> =
1022 std::sync::LazyLock::new(|| {
1023 #[allow(unused_mut)]
1024 let mut attrs = $crate::attrs::Attrs::new();
1025 $($(
1026 attrs.set($meta_key, $meta_value);
1029 )*)*
1030 attrs
1031 });
1032 }
1033
1034 $(#[$attr])*
1035 $vis static $name: $crate::attrs::Key<$type> = {
1036 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1037 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1038 $crate::attrs::Key::new(LOWER_NAME, None, $crate::paste! { &[<$name _META_ATTRS>] })
1039 };
1040
1041
1042 $crate::submit! {
1044 $crate::attrs::AttrKeyInfo {
1045 name: {
1046 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1047 $crate::const_ascii_lowercase!(FULL_NAME)
1048 },
1049 key_hash: {
1050 const FULL_NAME: &str = concat!(std::module_path!(), "::", stringify!($name));
1051 const LOWER_NAME: &str = $crate::const_ascii_lowercase!(FULL_NAME);
1052 $crate::attrs::fnv1a_hash(LOWER_NAME.as_bytes())
1053 },
1054 typehash: <$type as $crate::typeuri::Named>::typehash,
1055 deserialize_erased: |deserializer| {
1056 let value: $type = erased_serde::deserialize(deserializer)?;
1057 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1058 },
1059 deserialize_bincode: |bytes| {
1060 let value: $type = $crate::bincode::deserialize(bytes)?;
1061 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1062 },
1063 meta: $crate::paste! { &[<$name _META_ATTRS>] },
1064 display: |value: &dyn $crate::attrs::SerializableValue| {
1065 let value = value.as_any().downcast_ref::<$type>().unwrap();
1066 $crate::attrs::AttrValue::display(value)
1067 },
1068 parse: |value: &str| {
1069 let value: $type = $crate::attrs::AttrValue::parse(value)?;
1070 Ok(Box::new(value) as Box<dyn $crate::attrs::SerializableValue>)
1071 },
1072 default: None,
1073 erased: &$name,
1074 }
1075 }
1076 };
1077}
1078
1079pub use declare_attrs;
1080
1081#[cfg(test)]
1082mod tests {
1083 use std::time::Duration;
1084
1085 use super::*;
1086
1087 declare_attrs! {
1088 attr TEST_TIMEOUT: Duration;
1089 attr TEST_COUNT: u32;
1090 @meta(TEST_COUNT = 42)
1091 pub attr TEST_NAME: String;
1092 attr TEST_OPTION_COUNT: Option<u32>;
1093 attr TEST_OPTION_TIMEOUT: Option<Duration>;
1094 }
1095
1096 #[test]
1097 fn test_basic_operations() {
1098 let mut attrs = Attrs::new();
1099
1100 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1102 attrs.set(TEST_COUNT, 42u32);
1103 attrs.set(TEST_NAME, "test".to_string());
1104
1105 assert_eq!(attrs.get(TEST_TIMEOUT), Some(&Duration::from_secs(5)));
1106 assert_eq!(attrs.get(TEST_COUNT), Some(&42u32));
1107 assert_eq!(attrs.get(TEST_NAME), Some(&"test".to_string()));
1108
1109 assert!(attrs.contains_key(TEST_TIMEOUT));
1111 assert!(attrs.contains_key(TEST_COUNT));
1112 assert!(attrs.contains_key(TEST_NAME));
1113
1114 assert_eq!(attrs.len(), 3);
1116 assert!(!attrs.is_empty());
1117
1118 assert_eq!(TEST_NAME.attrs().get(TEST_COUNT).unwrap(), &42u32);
1120 }
1121
1122 #[test]
1123 fn test_get_mut() {
1124 let mut attrs = Attrs::new();
1125 attrs.set(TEST_COUNT, 10u32);
1126
1127 if let Some(count) = attrs.get_mut(TEST_COUNT) {
1128 *count += 5;
1129 }
1130
1131 assert_eq!(attrs.get(TEST_COUNT), Some(&15u32));
1132 }
1133
1134 #[test]
1135 fn test_remove() {
1136 let mut attrs = Attrs::new();
1137 attrs.set(TEST_COUNT, 42u32);
1138
1139 let removed = attrs.remove(TEST_COUNT);
1140 assert!(removed);
1141 assert_eq!(attrs.get(TEST_COUNT), None);
1142 assert!(!attrs.contains_key(TEST_COUNT));
1143 }
1144
1145 #[test]
1146 fn test_clear() {
1147 let mut attrs = Attrs::new();
1148 attrs.set(TEST_TIMEOUT, Duration::from_secs(1));
1149 attrs.set(TEST_COUNT, 42u32);
1150
1151 attrs.clear();
1152 assert!(attrs.is_empty());
1153 assert_eq!(attrs.len(), 0);
1154 }
1155
1156 #[test]
1157 fn test_key_properties() {
1158 assert_eq!(
1159 TEST_TIMEOUT.name(),
1160 "hyperactor_config::attrs::tests::test_timeout"
1161 );
1162 }
1163
1164 #[test]
1165 fn test_serialization() {
1166 let mut attrs = Attrs::new();
1167 attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1168 attrs.set(TEST_COUNT, 42u32);
1169 attrs.set(TEST_NAME, "test".to_string());
1170
1171 let serialized = serde_json::to_string(&attrs).expect("Failed to serialize");
1173
1174 assert!(serialized.contains("hyperactor_config::attrs::tests::test_timeout"));
1176 assert!(serialized.contains("hyperactor_config::attrs::tests::test_count"));
1177 assert!(serialized.contains("hyperactor_config::attrs::tests::test_name"));
1178 }
1179
1180 #[test]
1181 fn test_deserialization() {
1182 let mut original_attrs = Attrs::new();
1184 original_attrs.set(TEST_TIMEOUT, Duration::from_secs(5));
1185 original_attrs.set(TEST_COUNT, 42u32);
1186 original_attrs.set(TEST_NAME, "test".to_string());
1187
1188 let serialized = serde_json::to_string(&original_attrs).expect("Failed to serialize");
1190
1191 let deserialized_attrs: Attrs =
1193 serde_json::from_str(&serialized).expect("Failed to deserialize");
1194
1195 assert_eq!(
1197 deserialized_attrs.get(TEST_TIMEOUT),
1198 Some(&Duration::from_secs(5))
1199 );
1200 assert_eq!(deserialized_attrs.get(TEST_COUNT), Some(&42u32));
1201 assert_eq!(deserialized_attrs.get(TEST_NAME), Some(&"test".to_string()));
1202 }
1203
1204 #[test]
1205 fn test_roundtrip_serialization() {
1206 let mut original = Attrs::new();
1208 original.set(TEST_TIMEOUT, Duration::from_secs(10));
1209 original.set(TEST_COUNT, 5u32);
1210 original.set(TEST_OPTION_COUNT, Some(5u32));
1211 original.set(TEST_OPTION_TIMEOUT, None);
1212 original.set(TEST_NAME, "test-service".to_string());
1213
1214 let serialized = serde_json::to_string(&original).unwrap();
1216
1217 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1219
1220 assert_eq!(
1222 deserialized.get(TEST_TIMEOUT),
1223 Some(&Duration::from_secs(10))
1224 );
1225 assert_eq!(deserialized.get(TEST_COUNT), Some(&5u32));
1226 assert_eq!(deserialized.get(TEST_OPTION_COUNT), Some(&Some(5u32)));
1227 assert_eq!(deserialized.get(TEST_OPTION_TIMEOUT), Some(&None));
1228 assert_eq!(
1229 deserialized.get(TEST_NAME),
1230 Some(&"test-service".to_string())
1231 );
1232 }
1233
1234 #[test]
1235 fn test_empty_attrs_serialization() {
1236 let attrs = Attrs::new();
1237 let serialized = serde_json::to_string(&attrs).unwrap();
1238
1239 assert_eq!(serialized, "{}");
1241
1242 let deserialized: Attrs = serde_json::from_str(&serialized).unwrap();
1243
1244 assert!(deserialized.is_empty());
1245 }
1246
1247 #[test]
1248 fn test_format_independence() {
1249 let mut attrs = Attrs::new();
1251 attrs.set(TEST_COUNT, 42u32);
1252 attrs.set(TEST_NAME, "test".to_string());
1253
1254 let json_output = serde_json::to_string(&attrs).unwrap();
1256 let yaml_output = serde_yaml::to_string(&attrs).unwrap();
1257
1258 assert!(json_output.contains(":"));
1260 assert!(json_output.contains("\""));
1261
1262 assert!(json_output.contains("42"));
1264 assert!(!json_output.contains("\"42\""));
1265
1266 assert!(yaml_output.contains(":"));
1268 assert!(yaml_output.contains("42"));
1269
1270 assert!(!yaml_output.contains("\"42\""));
1272
1273 assert_ne!(json_output, yaml_output);
1275
1276 let from_json: Attrs = serde_json::from_str(&json_output).unwrap();
1278 let from_yaml: Attrs = serde_yaml::from_str(&yaml_output).unwrap();
1279
1280 assert_eq!(from_json.get(TEST_COUNT), Some(&42u32));
1281 assert_eq!(from_yaml.get(TEST_COUNT), Some(&42u32));
1282 assert_eq!(from_json.get(TEST_NAME), Some(&"test".to_string()));
1283 assert_eq!(from_yaml.get(TEST_NAME), Some(&"test".to_string()));
1284 }
1285
1286 #[test]
1287 fn test_clone() {
1288 let mut original = Attrs::new();
1290 original.set(TEST_COUNT, 42u32);
1291 original.set(TEST_NAME, "test".to_string());
1292 original.set(TEST_TIMEOUT, std::time::Duration::from_secs(10));
1293
1294 let cloned = original.clone();
1296
1297 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32));
1299 assert_eq!(cloned.get(TEST_NAME), Some(&"test".to_string()));
1300 assert_eq!(
1301 cloned.get(TEST_TIMEOUT),
1302 Some(&std::time::Duration::from_secs(10))
1303 );
1304
1305 original.set(TEST_COUNT, 100u32);
1307 assert_eq!(original.get(TEST_COUNT), Some(&100u32));
1308 assert_eq!(cloned.get(TEST_COUNT), Some(&42u32)); let mut cloned_mut = cloned.clone();
1312 cloned_mut.set(TEST_NAME, "modified".to_string());
1313 assert_eq!(cloned_mut.get(TEST_NAME), Some(&"modified".to_string()));
1314 assert_eq!(original.get(TEST_NAME), Some(&"test".to_string())); }
1316
1317 #[test]
1318 fn test_debug_with_json() {
1319 let mut attrs = Attrs::new();
1320 attrs.set(TEST_COUNT, 42u32);
1321 attrs.set(TEST_NAME, "test".to_string());
1322
1323 let debug_output = format!("{:?}", attrs);
1325
1326 assert!(debug_output.contains("Attrs"));
1328
1329 assert!(debug_output.contains("42"));
1331
1332 assert!(debug_output.contains("hyperactor_config::attrs::tests::test_count"));
1334 assert!(debug_output.contains("hyperactor_config::attrs::tests::test_name"));
1335
1336 assert!(debug_output.contains("test"));
1339 }
1340
1341 declare_attrs! {
1342 attr TIMEOUT_WITH_DEFAULT: Duration = Duration::from_secs(10);
1344
1345 pub(crate) attr CRATE_LOCAL_ATTR: String;
1347 }
1348
1349 #[test]
1350 fn test_defaults() {
1351 assert!(TIMEOUT_WITH_DEFAULT.has_default());
1352 assert!(!CRATE_LOCAL_ATTR.has_default());
1353
1354 assert_eq!(
1355 Attrs::new().get(TIMEOUT_WITH_DEFAULT),
1356 Some(&Duration::from_secs(10))
1357 );
1358 }
1359
1360 #[test]
1361 fn test_indexing() {
1362 let mut attrs = Attrs::new();
1363
1364 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(10));
1365 attrs[TIMEOUT_WITH_DEFAULT] = Duration::from_secs(100);
1366 assert_eq!(attrs[TIMEOUT_WITH_DEFAULT], Duration::from_secs(100));
1367
1368 attrs.set(CRATE_LOCAL_ATTR, "test".to_string());
1369 assert_eq!(attrs[CRATE_LOCAL_ATTR], "test".to_string());
1370 }
1371
1372 #[test]
1373 fn attrs_deserialize_unknown_key_is_error() {
1374 let bad_key: &'static str = "monarch_hyperactor::pytokio::unawaited_pytokio_traceback";
1399
1400 let mut attrs = Attrs::new();
1404 attrs.insert_value_by_name_unchecked(bad_key, Box::new(0u32));
1405
1406 let wire_bytes = bincode::serialize(&attrs).unwrap();
1409
1410 let err = bincode::deserialize::<Attrs>(&wire_bytes)
1413 .expect_err("should error on unknown attr key");
1414
1415 let exe_str = std::env::current_exe()
1416 .ok()
1417 .map(|p| p.display().to_string())
1418 .unwrap_or_else(|| "<unknown-exe>".to_string());
1419 let msg = format!("{err}");
1420
1421 assert!(msg.contains("unknown attr key"), "got: {msg}");
1422 assert!(msg.contains(bad_key), "got: {msg}");
1423 assert!(msg.contains(&exe_str), "got: {msg}");
1424 }
1425
1426 #[test]
1435 fn test_vec_string_attr() {
1436 declare_attrs! {
1437 attr TEST_VEC: Vec<String>;
1438 }
1439 let mut attrs = Attrs::new();
1440 let original = vec!["a".to_string(), "b".to_string()];
1441 attrs.set(TEST_VEC, original.clone());
1442 assert_eq!(attrs.get(TEST_VEC), Some(&original));
1443 let json = serde_json::to_string(&attrs).unwrap();
1445 let attrs2: Attrs = serde_json::from_str(&json).unwrap();
1446 assert_eq!(attrs2.get(TEST_VEC), Some(&original));
1447 let displayed = AttrValue::display(&original);
1449 let parsed: Vec<String> = AttrValue::parse(&displayed).unwrap();
1450 assert_eq!(parsed, original);
1451 }
1452}